GenerateContentUseCase.php

Code Hygiene Score: 98

Keine Issues gefunden.

Klassen 1

Funktionen 7

Verwendet von 2

Versionen 5

Code

<?php

declare(strict_types=1);

namespace UseCases\Content;

// @responsibility: Orchestriert Content-Generierung via Python-Pipeline

class GenerateContentUseCase
{
    private string $pipelinePath;
    private string $pythonPath;

    /** @var array<string> Allowed Python commands */
    private const ALLOWED_COMMANDS = ['generate', 'critique', 'revise'];

    public function __construct(
        ?string $pipelinePath = null,
        ?string $pythonPath = null
    ) {
        $this->pipelinePath = $pipelinePath ?? '/var/www/scripts/pipeline';
        $this->pythonPath = $pythonPath ?? $this->pipelinePath . '/venv/bin/python';
    }

    /**
     * Generate content for an order (synchronous)
     */
    public function generate(int $orderId, string $model, string $collection, int $contextLimit): ContentGenerationResult
    {
        $result = $this->callPython('generate', $orderId, [$model, $collection, $contextLimit]);

        return ContentGenerationResult::fromPythonResult($result);
    }

    /**
     * Start async content generation (returns immediately)
     *
     * Note: Python script reads DB password directly from .env via config.py
     * No need to pass credentials via environment variables
     */
    public function generateAsync(int $orderId, string $model, string $collection, int $contextLimit): bool
    {
        $scriptPath = $this->pipelinePath . '/web_generate.py';
        $logPath = '/tmp/content_gen_' . $orderId . '.log';

        $cmd = sprintf(
            'nohup %s %s generate %d %s %s %d > %s 2>&1 &',
            escapeshellarg($this->pythonPath),
            escapeshellarg($scriptPath),
            $orderId,
            escapeshellarg($model),
            escapeshellarg($collection),
            $contextLimit,
            escapeshellarg($logPath)
        );

        exec($cmd); // nosemgrep: exec-use

        return true;
    }

    /**
     * Run critique on a version (synchronous)
     */
    public function critique(int $versionId, string $model): ContentGenerationResult
    {
        $result = $this->callPython('critique', $versionId, [$model]);

        return ContentGenerationResult::fromPythonResult($result);
    }

    /**
     * Start async critique (returns immediately)
     *
     * Note: Python script reads DB password directly from .env via config.py
     */
    public function critiqueAsync(int $orderId, int $versionId, string $model): bool
    {
        $scriptPath = $this->pipelinePath . '/web_generate.py';
        $logPath = '/tmp/content_critique_' . $orderId . '.log';

        $cmd = sprintf(
            'nohup %s %s critique %d %s %d > %s 2>&1 &',
            escapeshellarg($this->pythonPath),
            escapeshellarg($scriptPath),
            $versionId,
            escapeshellarg($model),
            $orderId,
            escapeshellarg($logPath)
        );

        exec($cmd); // nosemgrep: exec-use

        return true;
    }

    /**
     * Create revision of a version
     */
    public function revise(int $versionId, string $model): ContentGenerationResult
    {
        $result = $this->callPython('revise', $versionId, [$model]);

        return ContentGenerationResult::fromPythonResult($result);
    }

    /**
     * Call Python script with command and arguments
     *
     * @param string $command Command to execute (generate, critique, revise)
     * @param int $entityId Order or version ID
     * @param array<mixed> $args Additional arguments
     * @return array<string,mixed> Result from Python script
     */
    private function callPython(string $command, int $entityId, array $args = []): array
    {
        // Validate command against whitelist
        if (!in_array($command, self::ALLOWED_COMMANDS, true)) {
            return ['error' => 'Ungültiger Command: ' . $command];
        }

        $scriptPath = $this->pipelinePath . '/web_generate.py';

        // Build command array for proc_open (safer than shell_exec)
        $cmdArray = [
            $this->pythonPath,
            $scriptPath,
            $command,
            (string) $entityId,
            ...$args,
        ];

        $descriptors = [
            0 => ['pipe', 'r'],
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];

        $process = proc_open($cmdArray, $descriptors, $pipes); // nosemgrep: exec-use

        if (!is_resource($process)) {
            return ['error' => 'Script konnte nicht gestartet werden'];
        }

        fclose($pipes[0]);
        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        $exitCode = proc_close($process);

        $output = $stdout . $stderr;

        if ($exitCode !== 0 && $output === '') {
            return ['error' => 'Script fehlgeschlagen (Exit: ' . $exitCode . ')'];
        }

        if (preg_match('/\{[\s\S]*\}/', $output, $matches)) {
            $result = json_decode($matches[0], true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $result;
            }
        }

        return ['error' => 'Ungültige Antwort: ' . substr($output, 0, 500)];
    }
}
← Übersicht Graph