Commit 68a508a6 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Suppression de UnicaenApp\Process\ProcessResult* et...

Suppression de UnicaenApp\Process\ProcessResult* et UnicaenApp\Service\SQL\RunSQL* (extraction vers une nouvelle bibliothèque unicaen/sql).
parent 2a8055ef
Loading
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
CHANGELOG
=========

6.1.5
-----
- Suppression de UnicaenApp\Process\ProcessResult* et UnicaenApp\Service\SQL\RunSQL* (extraction vers une nouvelle bibliothèque unicaen/sql).

6.1.4
-----
- Changement explode on NULL
+0 −3
Original line number Diff line number Diff line
@@ -26,8 +26,6 @@ use UnicaenApp\ORM\Query\Functions\PasHistorise;
use UnicaenApp\ORM\Query\Functions\Replace;
use UnicaenApp\Service\Mailer\MailerService;
use UnicaenApp\Service\Mailer\MailerServiceFactory;
use UnicaenApp\Service\SQL\RunSQLService;
use UnicaenApp\Service\SQL\RunSQLServiceFactory;
use UnicaenApp\ServiceManager\ServiceLocatorAwareInitializer;
use UnicaenApp\View\Helper\AppInfos;
use UnicaenApp\View\Helper\AppInfosFactory;
@@ -307,7 +305,6 @@ return [
            'MouchardCompleterMvc'         => 'UnicaenApp\Mouchard\MouchardCompleterMvcFactory',

            MailerService::class    => MailerServiceFactory::class,
            RunSQLService::class    => RunSQLServiceFactory::class,
            HostLocalization::class => HostLocalizationFactory::class,
            RedirectResponse::class => RedirectResponseFactory::class,

+0 −205
Original line number Diff line number Diff line
<?php

namespace UnicaenApp\Process;

use Exception;
use UnicaenApp\Exception\LogicException;
use UnicaenApp\Exception\RuntimeException;
use Laminas\Log\Formatter\Simple;
use Laminas\Log\Logger;
use Laminas\Log\LoggerInterface;
use Laminas\Log\Writer\Stream;
use Laminas\Log\Writer\WriterInterface;

/**
 * Classe permettant de représenter le résultat de l'exécution d'un processus inconnu quelconque.
 *
 * @see ProcessResultInterface
 *
 * @author Unicaen
 */
abstract class ProcessResult implements ProcessResultInterface
{
    /**
     * @var resource
     */
    private $logStream;

    /**
     * @var WriterInterface
     */
    private $logWriter;

    /**
     * @var bool
     */
    private $success;

    /**
     * @var Exception
     */
    private $exception;

    /**
     * @var float
     */
    private $startMicrotime;

    /**
     * @var float
     */
    private $endMicrotime;

    /**
     * RunSqlResult constructor.
     */
    public function __construct()
    {
        $format = '%message%'; // '%timestamp% %priorityName% (%priority%): %message%' . PHP_EOL;
        $formatter = new Simple($format);

        $this->logStream = fopen('php://memory','r+');

        $this->logWriter = new Stream($this->logStream);
        $this->logWriter->setFormatter($formatter);

        $this->setStartMicrotime();
    }

    public function __destruct()
    {
        // Test ajouté pour éviter l'étrange "Warning: fclose(): supplied resource is not a valid stream resource"
        if (is_resource($this->logStream)) {
            fclose($this->logStream);
        }
    }

    /**
     * Attache un logger pour stocker les éventuels logs qu'il génèrera.
     *
     * Concrètement, cela ajoute au logger spécifié 'writer' permettant qui stockera les logs
     * pour les restituer ultèrieurement via la méthode {@see getLog()}.
     *
     * @param LoggerInterface $logger
     * @return self
     */
    public function attachLogger(LoggerInterface $logger)
    {
        if (! $logger instanceof Logger) {
            throw new LogicException("Logger spécifié non supporté, désolé!");
        }

        $logger->addWriter($this->logWriter);

        return $this;
    }

    /**
     * Retourne les logs stockés.
     *
     * @return string
     */
    public function getLog()
    {
        $offset = ftell($this->logStream);

        rewind($this->logStream);
        $logs = stream_get_contents($this->logStream);
        fseek($this->logStream, $offset);

        return $logs;
    }

    /**
     * Retourne le booléen indiquant si l'exécution est couronnée de succès ou non.
     *
     * @return bool
     */
    public function isSuccess()
    {
        return $this->success;
    }

    /**
     * Positionne le booléen indiquant si l'exécution est couronnée de succès ou non.
     *
     * @param bool $success
     * @return self
     */
    public function setIsSuccess($success = true)
    {
        $this->success = (bool) $success;

        return $this;
    }

    /**
     * @return float
     */
    public function getStartMicrotime()
    {
        return $this->startMicrotime;
    }

    /**
     * @param float|null $startMicrotime
     */
    public function setStartMicrotime($startMicrotime = null)
    {
        $this->startMicrotime = $startMicrotime ?: microtime(true);
    }

    /**
     * @return float
     */
    public function getEndMicrotime()
    {
        return $this->endMicrotime;
    }

    /**
     * @param float|null $endMicrotime
     */
    public function setEndMicrotime($endMicrotime = null)
    {
        $this->endMicrotime = $endMicrotime ?: microtime(true);
    }

    /**
     * @return float
     */
    public function getDurationInSec()
    {
        $startMicrotime = $this->getStartMicrotime();
        $endMicrotime = $this->getEndMicrotime();

        if ($startMicrotime === null || $endMicrotime === null) {
            throw new RuntimeException("Impossible de calculer la durée car l'instant de début ou de fin est null !");
        }

        return $endMicrotime - $startMicrotime;
    }

    /**
     * Retourne l'éventuelle exception rencontrée lors de l'exécution.
     *
     * @return Exception
     */
    public function getException()
    {
        return $this->exception;
    }

    /**
     * Renseigne l'exception rencontrée lors de l'exécution.
     *
     * @param Exception $exception
     * @return self
     */
    public function setException(Exception $exception)
    {
        $this->exception = $exception;

        return $this;
    }
}
+0 −83
Original line number Diff line number Diff line
<?php

namespace UnicaenApp\Process;

use Exception;

/**
 * Interface décrivant le résultat de l'exécution d'un processus inconnu quelconque, çàd :
 * - un témoin de réussite ou non
 * - des logs d'exécution
 * - une exception éventuelle en cas d'erreur.
 * - une date de début d'exécution
 * - une date de fin d'exécution
 * - le calcul de la durée d'exécution
 *
 * @author Unicaen
 */
interface ProcessResultInterface
{
    /**
     * Retourne les logs.
     *
     * @return string
     */
    public function getLog();

    /**
     * Retourne le booléen indiquant si l'exécution est couronnée de succès ou non.
     *
     * @return bool
     */
    public function isSuccess();

    /**
     * Positionne le booléen indiquant si l'exécution est couronnée de succès ou non.
     *
     * @param bool $success
     * @return self
     */
    public function setIsSuccess($success = true);

    /**
     * @return float
     */
    public function getStartMicrotime();

    /**
     * @param float|null $startMicrotime
     * @return self
     */
    public function setStartMicrotime($startMicrotime = null);

    /**
     * @return float
     */
    public function getEndMicrotime();

    /**
     * @param float|null $endMicrotime
     * @return self
     */
    public function setEndMicrotime($endMicrotime = null);

    /**
     * @return float
     */
    public function getDurationInSec();

    /**
     * Retourne l'éventuelle exception rencontrée lors de l'exécution.
     *
     * @return null|Exception
     */
    public function getException();

    /**
     * Renseigne l'exception rencontrée lors de l'exécution.
     *
     * @param Exception $exception
     * @return self
     */
    public function setException(Exception $exception);
}
+0 −283
Original line number Diff line number Diff line
<?php

namespace UnicaenApp\Service\SQL;

use Doctrine\DBAL\Connection;
use Exception;
use Laminas\Log\LoggerAwareTrait;
use Laminas\Stdlib\Glob;
use UnicaenApp\Exception\RuntimeException;

class RunSQLProcess
{
    use LoggerAwareTrait;

    const QUERIES_SPLIT_PATTERN = "#^/$#m"; // oracle !!
    const LOG_FILE_EXT = '.log.sql';
    const LOG_FILE_EXT_PATTERN = '.log.*.sql';
    const LOG_FILE_EXT_TEMPLATE = '.log.%d.sql';

    /**
     * @var string
     */
    private $scriptPath;

    /**
     * @var string
     */
    private $logFilePath;

    /**
     * @var Connection
     */
    private $connection;

    /**
     * @var string
     */
    private $queriesSplitPattern = self::QUERIES_SPLIT_PATTERN;

    /**
     * @var string[]
     */
    private $queries;

    /**
     * @var RunSQLQueryStack
     */
    private $executedQueriesStack;

    /**
     * @param string $scriptPath
     * @return self
     */
    public function setScriptPath(string $scriptPath)
    {
        $this->scriptPath = $scriptPath;

        return $this;
    }

    /**
     * @param Connection $connection
     * @return RunSQLProcess
     */
    public function setConnection(Connection $connection)
    {
        $this->connection = $connection;

        return $this;
    }

    /**
     * @param null|string $logFilePath
     * @return self
     */
    public function setLogFilePath($logFilePath = null)
    {
        $this->logFilePath = $logFilePath;

        return $this;
    }

    /**
     * @param string $queriesSplitPattern
     * @return self
     */
    public function setQueriesSplitPattern($queriesSplitPattern)
    {
        $this->queriesSplitPattern = $queriesSplitPattern;

        return $this;
    }

    /**
     * Exécute dans la transaction courante toutes les instructions d'un script SQL.
     *
     * @return RunSQLResult
     */
    public function executeScript()
    {
        $this->validateScriptPath();
        $this->extractQueriesFromScript();

        $this->logger->info("+ Exécution du script '$this->scriptPath'.");
        $this->logger->info(sprintf("'--> Requêtes trouvées : %d", count($this->queries)));

        $result = $this->executeQueries();
        $this->createLogFile();

        return $result;
    }

    /**
     * Exécute dans la transaction courante toutes les instructions d'un script SQL.
     *
     * @param string $query
     * @return RunSQLResult
     */
    public function executeQuery(string $query)
    {
        $this->logger->info("+ Exécution d'une requête.");

        $this->queries = [$query];
        $result = $this->executeQueries();
        $this->createLogFile();

        return $result;
    }

    private function validateScriptPath()
    {
        if (is_dir($this->scriptPath)) {
            throw new RuntimeException("Le fichier '$this->scriptPath' spécifié est un répertoire");
        }
        if (!is_readable($this->scriptPath)) {
            throw new RuntimeException("Le fichier '$this->scriptPath' n'est pas accessible");
        }
    }

    /**
     * Extrait les requêtes contenues dans le script.
     */
    protected function extractQueriesFromScript()
    {
        $parts = preg_split($this->queriesSplitPattern, file_get_contents($this->scriptPath));
        $queries = array_filter(array_map('trim', $parts));

        if (count($queries) === 0) {
            throw new RuntimeException("Aucune requête trouvée dans le script '$this->scriptPath'");
        }

        $this->queries = $queries;
    }

    /**
     * Exécute dans la transaction courante les requêtes spécifiées.
     *
     * @return RunSQLResult
     */
    private function executeQueries()
    {
        $result = new RunSQLResult();
        $result->setScriptPath($this->scriptPath);
        $result->setLogFilePath($this->logFilePath);
        $result->attachLogger($this->logger);
        $result->setIsSuccess(true);

        $this->executedQueriesStack = new RunSQLQueryStack();

        try {
            foreach ($this->queries as $query) {
                $this->executedQueriesStack->startQuery($query);
                $this->connection->executeQuery($query);
                $this->executedQueriesStack->stopQuery();
            }
        } catch (\Doctrine\DBAL\Exception $e) {
            $result->setIsSuccess(false);
            $result->setException($e);

            $this->executedQueriesStack->stopQueryWithException($e);
        }

        $result->setEndMicrotime();

        $this->logger->info(sprintf("'--> Requêtes exécutées : %d", count($this->executedQueriesStack->getQueries())));

        return $result;
    }

    private function createLogFile()
    {
        $logFilePath = $this->computeLogFilePath();

        $executedQueries = $this->executedQueriesStack->getQueries();
        $remainingQueries = $this->computeRemainingQueries();

        $comment = function($line, $with = '--') {
            return $with . ' ' . str_replace(PHP_EOL, PHP_EOL . $with . ' ', $line);
        };

        $title = $this->scriptPath ?
            "Log d'exécution du script SQL '{$this->scriptPath}'." :
            "Log d'exécution d'une requête SQL.";

        $lines = [];
        $lines[] = "----------------------------------------------------------------------------------------------";
        $lines[] = "--";
        $lines[] = "-- $title";
        $lines[] = "--";
        $lines[] = "-- " . date_create()->format('d/m/Y H:m:s');
        $lines[] = "--";
        $lines[] = "----------------------------------------------------------------------------------------------";
        $lines[] = "";
        $lines[] = "";
        $lines[] = "--------------------- REQUÊTES EXÉCUTÉES ---------------------";
        $lines[] = "";
        foreach ($executedQueries as $query) {
            $hasSucceeded = ! isset($query['exception']);
            if ($hasSucceeded) {
                $lines[] = $comment($query['sql']);
                $lines[] = $comment("/");
                $lines[] = $comment("SUCCÈS");
            } else {
                $exception = $query['exception']; /** @var Exception $exception */
                $lines[] = $comment($query['sql']);
                $lines[] = $comment("/");
                $lines[] = $comment("ÉCHEC", '------');
                $lines[] = $comment("=====", '------');
                $lines[] = $comment($exception->getMessage(), '------');
                $lines[] = $comment("=====", '------');
            }
            $lines[] = $comment($query['executionMS'] . " sec");
            $lines[] = "";
        }
        $lines[] = "";
        $lines[] = "";
        $lines[] = "--------------------- REQUÊTES RESTANTES ---------------------";
        $lines[] = "";
        foreach ($remainingQueries as $query) {
            $lines[] = $query;
            $lines[] = "/";
        }

        file_put_contents($logFilePath, implode(PHP_EOL, $lines));

        $this->logger->info(sprintf("'--> Log script : %s", $logFilePath));
    }

    /**
     * @return string[]
     */
    private function computeRemainingQueries()
    {
        $executedQueries = $this->executedQueriesStack->getQueries();

        $offset = count($executedQueries);
        if ($this->executedQueriesStack->lastQueryHasException()) {
            $offset--; // si la dernière requête a échouée, on la remet quand même dans la liste des requêtes restantes
        }

        return array_slice($this->queries, $offset, null, true);
    }

    /**
     * @return string
     */
    private function computeLogFilePath()
    {
        if ($this->logFilePath !== null) {
            return $this->logFilePath;
        }

        $dir = sys_get_temp_dir();
        $scriptName = $this->scriptPath ? basename($this->scriptPath) : 'run-sql-query';

        $filepathPattern = $dir . '/' . $scriptName . self::LOG_FILE_EXT_PATTERN;
        $filepathTemplate = $dir . '/' . $scriptName . self::LOG_FILE_EXT_TEMPLATE;

        $existingFiles = Glob::glob($filepathPattern);

        return sprintf($filepathTemplate, count($existingFiles) + 1);
    }
}
 No newline at end of file
Loading