Skip to content
Snippets Groups Projects
Select Git revision
  • master
  • unicaen_authentification
  • release_3.1.0
  • 3.x
  • 2.x
  • 4.0.0
  • 3.1.0
  • 2.0.1
  • 3.0.0
  • 2.0.0
  • 1.0.0
11 results

README.md

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    RunSQLProcess.php 7.47 KiB
    <?php
    
    namespace UnicaenApp\Service\SQL;
    
    use Doctrine\DBAL\Connection;
    use Exception;
    use UnicaenApp\Exception\RuntimeException;
    use Zend\Log\LoggerAwareTrait;
    use Zend\Stdlib\Glob;
    
    class RunSQLProcess
    {
        use LoggerAwareTrait;
    
        const QUERIES_SPLIT_PATTERN = "#^/$#m";
        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 $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;
        }
    
        /**
         * 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(self::QUERIES_SPLIT_PATTERN, 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->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);
        }
    }