dsn) ? 'none' : $this->dsn; } /** * Whether the database connection has been set up yet * * @return bool */ public function isSetup(): bool { return $this->setUp; } /** * Method called by \FuzeWorks\Database to setUp the database connection * * @param array $parameters * @return bool * @throws DatabaseException */ public function setUp(array $parameters): bool { // Prepare variables for connection $this->dsn = isset($parameters['dsn']) ? $parameters['dsn'] : null; $username = isset($parameters['username']) ? $parameters['username'] : ''; $password = isset($parameters['password']) ? $parameters['password'] : ''; // Don't attempt connection without DSN if (is_null($this->dsn)) throw new DatabaseException("Could not setUp PDOEngine. No DSN provided"); // Set some base parameters which are required for FuzeWorks $parameters[PDO::ATTR_ERRMODE] = PDO::ERRMODE_SILENT; // Attempt to connect. Throw exception on failure try { $this->pdoConnection = new PDO($this->dsn, $username, $password, $parameters); } catch (PDOException $e) { throw new DatabaseException("Could not setUp PDOEngine. PDO threw PDOException: '" . $e->getMessage() . "'"); } // Set this engine as set up $this->setUp = true; // And return true upon success return true; } /** * Method called by \FuzeWorks\Database to tear down the database connection upon shutdown * * @return bool */ public function tearDown(): bool { // Commit or rollback all changes to the database $this->transactionEnd(); // $this->pdoConnection = null; return true; } /** * Call methods on the PDO Connection * * @param $name * @param $arguments * @return mixed */ public function __call($name, $arguments) { return $this->pdoConnection->{$name}(...$arguments); } /** * Get properties from the PDO Connection * * @param $name * @return mixed */ public function __get($name) { return $this->pdoConnection->$name; } /** * Set properties on the PDO Connection * * @param $name * @param $value * @return mixed */ public function __set($name, $value) { return $this->pdoConnection->$name = $value; } /** * Internal method used to report queries by the \FuzeWorks\DatabaseEngine\PDOStatement * * @internal * @param string $queryString * @param int $queryData * @param float $queryTimings */ public function logPDOQuery(string $queryString, int $queryData, float $queryTimings, $errorInfo = []) { $errorInfo = empty($errorInfo) ? $this->error() : $errorInfo; $this->logQuery($queryString, $queryData, $queryTimings, $errorInfo); } /** * Perform a query with the database. Only supports queries. * * Should only be used for reading data without dynamic statements. * * @param string $sql * @return PDOStatement * @throws DatabaseException */ public function query(string $sql): PDOStatement { if (empty($sql)) throw new DatabaseException("Could not run query. Provided query is empty."); // Run the query and benchmark the time $benchmarkStart = microtime(true); $result = $this->pdoConnection->query($sql); $benchmarkEnd = microtime(true) - $benchmarkStart; // Log the query $this->logPDOQuery($sql, [], $benchmarkEnd); // If the query failed, handle the error if ($result === false) { // Mark the transaction as failed $this->transactionFailed = true; // And throw an exception throw new DatabaseException("Could not run query. Database returned an error. Error code: " . $this->error()['code']); } return $result; } /** * Create a PDOStatement to alter data on the database. * * @param string $statement * @param array $driver_options * @return PDOStatementWrapper */ public function prepare(string $statement, array $driver_options = []): PDOStatementWrapper { return new PDOStatementWrapper( $this->pdoConnection->prepare($statement, $driver_options), array($this, 'logPDOQuery') ); } /** * Generates an error message for the last failure in PDO * * @return array */ protected function error(): array { $error = []; $pdoError = $this->pdoConnection->errorInfo(); if (empty($pdoError[0]) || $pdoError[0] == '00000') return $error; $error['code'] = isset($pdoError[1]) ? $pdoError[0] . '/' . $pdoError[1] : $pdoError[0]; if (isset($pdoError[2])) $error['message'] = $pdoError[2]; return $error; } /** * Start a transaction * * @return bool */ public function transactionStart(): bool { return $this->pdoConnection->beginTransaction(); } /** * End a transaction. * * Only runs of autocommit is enabled; and a transaction is running. * Automatically rolls back changes if an error occurs with a query * * @return bool */ public function transactionEnd(): bool { // If autocommit is disabled, don't do anything if (!$this->transactionAutocommit) return false; // If there is no transaction, there is nothing to rollback if (!$this->pdoConnection->inTransaction()) return false; // If a transaction has failed, it should be rolled back if ($this->transactionFailed === true) { $this->transactionRollback(); Logger::logError("PDOEngine transaction failed. Transaction has been rolled back."); } return $this->transactionCommit(); } /** * Commit a transaction * * @return bool */ public function transactionCommit(): bool { return $this->pdoConnection->commit(); } /** * Roll back a transaction * * @return bool */ public function transactionRollback(): bool { return $this->pdoConnection->rollBack(); } }