Subversion Repositories oidplus

Rev

Rev 260 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. if (!defined('IN_OIDPLUS')) die();
  21.  
  22. class OIDplusDatabasePluginPDO extends OIDplusDatabasePlugin {
  23.         private $conn = null;
  24.         private $last_error = null; // we need that because PDO divides prepared statement errors and normal query errors, but we have only one "error()" method
  25.  
  26.         public static function getPluginInformation(): array {
  27.                 $out = array();
  28.                 $out['name'] = 'PDO';
  29.                 $out['author'] = 'ViaThinkSoft';
  30.                 $out['version'] = null;
  31.                 $out['descriptionHTML'] = null;
  32.                 return $out;
  33.         }
  34.  
  35.         public static function name(): string {
  36.                 return "PDO";
  37.         }
  38.  
  39.         public function doQuery(string $sql, /*?array*/ $prepared_args=null): OIDplusQueryResult {
  40.                 $this->last_error = null;
  41.                 if (is_null($prepared_args)) {
  42.                         $res = $this->conn->query($sql);
  43.  
  44.                         if ($res === false) {
  45.                                 $this->last_error = $this->conn->errorInfo()[2];
  46.                                 throw new OIDplusSQLException($sql, $this->error());
  47.                         } else {
  48.                                 return new OIDplusQueryResultPDO($res);
  49.                         }
  50.                 } else {
  51.                         // TEST: Emulate the prepared statement
  52.                         /*
  53.                         foreach ($prepared_args as $arg) {
  54.                                 $needle = '?';
  55.                                 $replace = "'$arg'"; // TODO: types
  56.                                 $pos = strpos($sql, $needle);
  57.                                 if ($pos !== false) {
  58.                                         $sql = substr_replace($sql, $replace, $pos, strlen($needle));
  59.                                 }
  60.                         }
  61.                         return OIDplusQueryResultPDO($this->conn->query($sql));
  62.                         */
  63.  
  64.                         if (!is_array($prepared_args)) {
  65.                                 throw new OIDplusException("'prepared_args' must be either NULL or an ARRAY.");
  66.                         }
  67.  
  68.                         foreach ($prepared_args as &$value) {
  69.                                 // We need to manually convert booleans into strings, because there is a
  70.                                 // 14 year old bug that hasn't been adressed by the PDO developers:
  71.                                 // https://bugs.php.net/bug.php?id=57157
  72.                                 // Note: We are using '1' and '0' instead of 'true' and 'false' because MySQL converts boolean to tinyint(1)
  73.                                 if (is_bool($value)) $value = $value ? '1' : '0';
  74.                         }
  75.  
  76.                         $ps = $this->conn->prepare($sql);
  77.                         if (!$ps) {
  78.                                 throw new OIDplusSQLException($sql, 'Cannot prepare statement');
  79.                         }
  80.                         $this->prepare_cache[$sql] = $ps;
  81.  
  82.                         if (!$ps->execute($prepared_args)) {
  83.                                 $this->last_error = $ps->errorInfo()[2];
  84.                                 throw new OIDplusSQLException($sql, $this->error());
  85.                         }
  86.                         return new OIDplusQueryResultPDO($ps);
  87.                 }
  88.         }
  89.  
  90.         public function insert_id(): int {
  91.                 return $this->conn->lastInsertId();
  92.         }
  93.  
  94.         public function error(): string {
  95.                 $err = $this->last_error;
  96.                 if ($err == null) $err = '';
  97.                 return $err;
  98.         }
  99.  
  100.         protected function doConnect(): void {
  101.                 if (!class_exists('PDO')) throw new OIDplusConfigInitializationException('PHP extension "PDO" not installed');
  102.  
  103.                 try {
  104.                         $options = [
  105.                         #    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
  106.                             PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  107.                             PDO::ATTR_EMULATE_PREPARES   => true,
  108.                         ];
  109.  
  110.                         // Try connecting to the database
  111.                         $dsn      = OIDplus::baseConfig()->getValue('PDO_DSN',      'mysql:host=localhost;dbname=oidplus;CHARSET=UTF8');
  112.                         $username = OIDplus::baseConfig()->getValue('PDO_USERNAME', 'root');
  113.                         $password = OIDplus::baseConfig()->getValue('PDO_PASSWORD', '');
  114.                         $this->conn = new PDO($dsn, $username, $password, $options);
  115.                 } catch (PDOException $e) {
  116.                         $message = $e->getMessage();
  117.                         throw new OIDplusConfigInitializationException('Connection to the database failed! '.$message);
  118.                 }
  119.  
  120.                 $this->query("SET NAMES 'utf8'");
  121.         }
  122.  
  123.         protected function doDisconnect(): void {
  124.                 $this->conn = null; // the connection will be closed by removing the reference
  125.         }
  126.  
  127.         private $intransaction = false;
  128.  
  129.         public function transaction_begin(): void {
  130.                 if ($this->intransaction) throw new OIDplusException("Nested transactions are not supported by this database plugin.");
  131.                 $this->conn->beginTransaction();
  132.                 $this->intransaction = true;
  133.         }
  134.  
  135.         public function transaction_commit(): void {
  136.                 $this->conn->commit();
  137.                 $this->intransaction = false;
  138.         }
  139.  
  140.         public function transaction_rollback(): void {
  141.                 $this->conn->rollBack();
  142.                 $this->intransaction = false;
  143.         }
  144. }
  145.  
  146. class OIDplusQueryResultPDO extends OIDplusQueryResult {
  147.         protected $no_resultset;
  148.         protected $res;
  149.  
  150.         public function __construct($res) {
  151.                 $this->no_resultset = is_bool($res);
  152.  
  153.                 if (!$this->no_resultset) {
  154.                         $this->res = $res;
  155.                 }
  156.         }
  157.  
  158.         public function __destruct() {
  159.                 if ($this->res) $this->res->closeCursor();
  160.         }
  161.  
  162.         public function containsResultSet(): bool {
  163.                 return !$this->no_resultset;
  164.         }
  165.  
  166.         public function num_rows(): int {
  167.                 if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
  168.                 return $this->res->rowCount();
  169.         }
  170.  
  171.         public function fetch_array()/*: ?array*/ {
  172.                 if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
  173.                 $ret = $this->res->fetch(PDO::FETCH_ASSOC);
  174.                 if ($ret === false) $ret = null;
  175.                 return $ret;
  176.         }
  177.  
  178.         public function fetch_object()/*: ?object*/ {
  179.                 if ($this->no_resultset) throw new OIDplusException("The query has returned no result set (i.e. it was not a SELECT query)");
  180.                 $ret = $this->res->fetch(PDO::FETCH_OBJ);
  181.                 if ($ret === false) $ret = null;
  182.                 return $ret;
  183.         }
  184. }
  185.