Browse Source

Merge pull request #55 from Smudge3806/fix/mongo_transactions

[WIP] [#48] Added defensive code for DatabaseConnection drivers that don't support transactions
Shalvah 4 years ago
parent
commit
5eb3fd1663

+ 5 - 0
config/scribe.php

@@ -331,4 +331,9 @@ INTRO
      *
      */
     'routeMatcher' => \Knuckles\Scribe\Matching\RouteMatcher::class,
+
+    /**
+     * [Advanced usage] If a database driver does not support transactions, you can list it here to allow it to run.
+     */
+    'run_without_database_transactions' => [],
 ];

+ 18 - 0
src/Exceptions/DbTransactionSupportException.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace Knuckles\Scribe\Exceptions;
+
+use Knuckles\Scribe\Exceptions\ScribeException;
+use RuntimeException;
+
+class DbTransactionSupportException extends RuntimeException implements ScribeException
+{
+    public static function create(string $connection_name, string $driver_name)
+    {
+        return new self(
+            "Database Driver [{$driver_name}] for connection [{$connection_name}] does not support transactions. " .
+            "Changes to your database will be persistent. " .
+            "To allow this, add \"{$connection_name}\" to the \"run_without_database_transactions\" config."
+        );
+    }
+}

+ 7 - 0
src/Exceptions/ScribeException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Knuckles\Scribe\Exceptions;
+
+interface ScribeException
+{
+}

+ 76 - 2
src/Extracting/DatabaseTransactionHelpers.php

@@ -3,6 +3,9 @@
 namespace Knuckles\Scribe\Extracting;
 
 use Exception;
+use Knuckles\Scribe\Exceptions\DbTransactionSupportException;
+use Knuckles\Scribe\Exceptions\ScribeException;
+use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
 
 trait DatabaseTransactionHelpers
 {
@@ -15,7 +18,22 @@ trait DatabaseTransactionHelpers
 
         foreach ($connections as $conn) {
             try {
-                app('db')->connection($conn)->beginTransaction();
+                $driver = app('db')->connection($conn);
+
+                if (self::driverSupportsTransactions($driver)) {
+                    $driver->beginTransaction();
+
+                    return;
+                }
+
+                if ($this->isNoTransactionSupportAllowed($conn)) {
+                    throw DbTransactionSupportException::create($conn, get_class($driver));
+                }
+
+                c::warn("Database driver for the connection [{$conn}] does not support transactions!");
+                c::warn("Any changes made to your database will persist!");
+            } catch (ScribeException $e) {
+                throw $e;
             } catch (Exception $e) {
             }
         }
@@ -30,9 +48,65 @@ trait DatabaseTransactionHelpers
 
         foreach ($connections as $conn) {
             try {
-                app('db')->connection($conn)->rollBack();
+                $driver = app('db')->connection($conn);
+
+                if (self::driverSupportsTransactions($driver)) {
+                    $driver->rollBack();
+
+                    return;
+                }
+
+                c::warn("Database driver for the connection [{$conn}] does not support transactions!");
+                c::warn("Any changes made to your database will persist!");
             } catch (Exception $e) {
             }
         }
     }
+
+    /**
+     * Assesses whether or not the "PDO" driver provided supports transactions
+     *
+     * @param mixed $driver Driver prodicted for particular connection
+     *
+     * @return boolean
+     */
+    private static function driverSupportsTransactions($driver)
+    {
+        $methods = [ 'beginTransaction', 'rollback' ];
+
+        foreach ($methods as $method) {
+            if (! method_exists($driver, $method)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Assesses whether drivers without transaction support can proceed
+     *
+     * @param string $connection_name Name of the connection
+     *
+     * @return boolean
+     */
+    private function isNoTransactionSupportAllowed(string $connection_name)
+    {
+        $config = $this->getConfig();
+
+        $allow_list = $config->get('run_without_database_transactions', false);
+
+        if (is_array($allow_list)) {
+            return in_array($connection_name, $allow_list);
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns an instance of the documentation config
+     *
+     * @return DocumentationConfig
+     */
+    abstract public function getConfig();
 }

+ 10 - 0
src/Extracting/Strategies/Strategy.php

@@ -24,6 +24,16 @@ abstract class Strategy
         $this->config = $config;
     }
 
+    /**
+     * Returns an instance of the documentation config
+     *
+     * @return DocumentationConfig
+     */
+    public function getConfig()
+    {
+        return $this->config;
+    }
+
     /**
      * @param Route $route The route which we are currently extracting information for.
      * @param ReflectionClass $controller The class handling the current route.