In debug mode: Throw errors if reading from prepared statements without executing...
authorAlexander Ebert <ebert@woltlab.com>
Sat, 9 Mar 2019 18:53:20 +0000 (19:53 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 9 Mar 2019 18:53:20 +0000 (19:53 +0100)
See #2819

wcfsetup/install/files/lib/system/database/Database.class.php
wcfsetup/install/files/lib/system/database/statement/DebugPreparedStatement.class.php [new file with mode: 0644]

index 37120ee87b4fed3e3855abaefe42e3d017d3c490..0d6f6b73f9620ca1a703a9abed714df9182d0a9e 100644 (file)
@@ -5,6 +5,7 @@ use wcf\system\database\editor\DatabaseEditor;
 use wcf\system\database\exception\DatabaseException as GenericDatabaseException;
 use wcf\system\database\exception\DatabaseQueryException;
 use wcf\system\database\exception\DatabaseTransactionException;
+use wcf\system\database\statement\DebugPreparedStatement;
 use wcf\system\database\statement\PreparedStatement;
 use wcf\system\WCF;
 
@@ -115,6 +116,10 @@ abstract class Database {
                $this->failsafeTest = $failsafeTest;
                $this->tryToCreateDatabase = $tryToCreateDatabase;
                
+               if (defined('ENABLE_DEBUG_MODE') && ENABLE_DEBUG_MODE) {
+                       $this->preparedStatementClassName = DebugPreparedStatement::class;
+               }
+               
                // connect database
                $this->connect();
        }
diff --git a/wcfsetup/install/files/lib/system/database/statement/DebugPreparedStatement.class.php b/wcfsetup/install/files/lib/system/database/statement/DebugPreparedStatement.class.php
new file mode 100644 (file)
index 0000000..43073a8
--- /dev/null
@@ -0,0 +1,98 @@
+<?php
+namespace wcf\system\database\statement;
+
+/**
+ * Similar to the regular `PreparedStatement` class, but throws an exception when trying to read data
+ * before executing the statement at least once.
+ * 
+ * @author      Alexander Ebert
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package     WoltLabSuite\Core\System\Database\Statement
+ */
+class DebugPreparedStatement extends PreparedStatement {
+       protected $debugDidExecuteOnce = false;
+       
+       /**
+        * @inheritDoc
+        */
+       public function __call($name, $arguments) {
+               if ($name === 'fetchAll' || $name === 'fetchColumn') {
+                       $this->debugThrowIfNotExecutedBefore();
+               }
+               
+               return parent::__call($name, $arguments);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute(array $parameters = []) {
+               $this->debugDidExecuteOnce = true;
+               
+               parent::execute($parameters);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchArray($type = null) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchArray($type);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchSingleRow($type = null) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchSingleRow($type);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchSingleColumn($columnNumber = 0) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchSingleColumn($columnNumber);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchObject($className) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchObject($className);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchObjects($className, $keyProperty = null) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchObjects($className, $keyProperty);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function fetchMap($keyColumn, $valueColumn, $uniqueKey = true) {
+               $this->debugThrowIfNotExecutedBefore();
+               
+               return parent::fetchMap($keyColumn, $valueColumn, $uniqueKey);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function debugThrowIfNotExecutedBefore() {
+               if (!$this->debugDidExecuteOnce) {
+                       throw new \RuntimeException('Attempted to fetch data from a statement without executing it at least once.');
+               }
+       }
+}