Subversion Repositories oidplus

Rev

Rev 1240 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2023 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. namespace ViaThinkSoft\OIDplus;
  21.  
  22. // phpcs:disable PSR1.Files.SideEffects
  23. \defined('INSIDE_OIDPLUS') or die;
  24. // phpcs:enable PSR1.Files.SideEffects
  25.  
  26. class OIDplusDatabaseConnectionSqlSrv extends OIDplusDatabaseConnection {
  27.  
  28.         /**
  29.          * @var mixed|null
  30.          */
  31.         private $conn = null;
  32.  
  33.         /**
  34.          * @var string|null
  35.          */
  36.         private $last_error = null;
  37.  
  38.         /**
  39.          * @var int
  40.          */
  41.         private $rowsAffected = 0;
  42.  
  43.         /**
  44.          * @return int
  45.          */
  46.         public function rowsAffected(): int {
  47.                 return $this->rowsAffected;
  48.         }
  49.  
  50.         /**
  51.          * @param string $sql
  52.          * @param array|null $prepared_args
  53.          * @return OIDplusQueryResultSqlSrv
  54.          * @throws OIDplusException
  55.          */
  56.         public function doQuery(string $sql, array $prepared_args=null): OIDplusQueryResult {
  57.                 $this->last_error = null;
  58.                 try {
  59.                         $res = sqlsrv_query($this->conn, $sql, $prepared_args,
  60.                                 array(
  61.                                         // SQLSRV_CURSOR_FORWARD ('forward', default)
  62.                                         // Lets you move one row at a time starting at the first row of the result set until you reach the end of the result set.
  63.                                         // => Does not work with sqlsrv_num_rows();
  64.  
  65.                                         // SQLSRV_CURSOR_STATIC ('static')
  66.                                         // Lets you access rows in any order but will not reflect changes in the database.
  67.                                         // => Does not work with transaction rollback?! (Testcase failed)
  68.  
  69.                                         // SQLSRV_CURSOR_DYNAMIC
  70.                                         // Lets you access rows in any order and will reflect changes in the database.
  71.                                         // => Does not work with sqlsrv_num_rows();
  72.  
  73.                                         // SQLSRV_CURSOR_KEYSET ('keyset')
  74.                                         // Lets you access rows in any order. However, a keyset cursor does not update the row count if a row is deleted from the table (a deleted row is returned with no values).
  75.                                         // => Does not work with transaction rollback?! (Testcase failed)
  76.  
  77.                                         // SQLSRV_CURSOR_CLIENT_BUFFERED ('buffered')
  78.                                         // Lets you access rows in any order. Creates a client-side cursor query.
  79.                                         // => Seems to work fine
  80.  
  81.                                         'Scrollable' => SQLSRV_CURSOR_CLIENT_BUFFERED
  82.                                 )
  83.                         );
  84.                 } catch (\Exception $e) {
  85.                         $this->last_error = $e->getMessage();
  86.                         throw new OIDplusSQLException($sql, $e->getMessage());
  87.                 }
  88.  
  89.                 if ($res === false) {
  90.                         $this->last_error = print_r(sqlsrv_errors(), true);
  91.                         throw new OIDplusSQLException($sql, $this->error());
  92.                 } else {
  93.                         if (str_starts_with(trim(strtolower($sql)),'select')) { // Note: Please do not call $this->getSlang()->fetchableRowsExpected($sql)
  94.                                 $this->rowsAffected = sqlsrv_num_rows($res);
  95.                         } else {
  96.                                 $this->rowsAffected = sqlsrv_rows_affected($res);
  97.                         }
  98.                         return new OIDplusQueryResultSqlSrv($res);
  99.                 }
  100.         }
  101.  
  102.  
  103.         /**
  104.          * @return string
  105.          */
  106.         public function error(): string {
  107.                 $err = $this->last_error;
  108.                 if ($err === null) $err = '';
  109.                 return $err;
  110.         }
  111.  
  112.         /**
  113.          * @return string
  114.          */
  115.         private static function get_sqlsrv_dll_name(): string {
  116.                 ob_start();
  117.                 phpinfo(INFO_GENERAL);
  118.                 $x = ob_get_contents();
  119.                 ob_end_clean();
  120.  
  121.                 $architecture =
  122.                         preg_match('@Architecture.+(x86|x64)@', $x, $m) ? $m[1] : '*';
  123.  
  124.                 $threadsafety =
  125.                         preg_match('@Thread Safety.+(enabled|disabled)@', $x, $m)
  126.                         ? ($m[1] == 'enabled' ? 'ts' : 'nts') : '*';
  127.  
  128.                 $m = explode('.',phpversion());
  129.                 $version = $m[0].$m[1];
  130.  
  131.                 // e.g. php_sqlsrv_82_ts_x64.dll
  132.                 return "php_sqlsrv_{$version}_{$threadsafety}_{$architecture}.dll";
  133.         }
  134.  
  135.         /**
  136.          * @return void
  137.          * @throws OIDplusConfigInitializationException
  138.          * @throws OIDplusException
  139.          */
  140.         protected function doConnect()/*: void*/ {
  141.                 // Download here: https://learn.microsoft.com/en-us/sql/connect/php/download-drivers-php-sql-server?view=sql-server-ver16
  142.                 if (!function_exists('sqlsrv_connect')) throw new OIDplusException(_L('PHP extension "%1" not installed',self::get_sqlsrv_dll_name()));
  143.  
  144.                 // Try connecting to the database
  145.                 $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
  146.                 $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
  147.                 $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
  148.                 $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
  149.                 $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
  150.  
  151.                 if (!isset($options['Database'])) $options['Database'] = $database;
  152.                 if (!isset($options['CharacterSet'])) $options['CharacterSet'] = 'UTF-8';
  153.                 if ($username != '') {
  154.                         if (!isset($options['UID'])) $options['UID'] = $username;
  155.                         if (!isset($options['PWD'])) $options['PWD'] = $password;
  156.                 }
  157.  
  158.                 $this->conn = @sqlsrv_connect($servername, $options);
  159.  
  160.                 if (!$this->conn) {
  161.                         $message = print_r(sqlsrv_errors(), true);
  162.                         throw new OIDplusConfigInitializationException(trim(_L('Connection to the database failed!').' '.$message));
  163.                 }
  164.  
  165.                 $this->last_error = null;
  166.         }
  167.  
  168.         /**
  169.          * @return void
  170.          */
  171.         protected function doDisconnect()/*: void*/ {
  172.                 if (!is_null($this->conn)) {
  173.                         sqlsrv_close($this->conn);
  174.                         $this->conn = null;
  175.                 }
  176.         }
  177.  
  178.         /**
  179.          * @var bool
  180.          */
  181.         private $intransaction = false;
  182.  
  183.         /**
  184.          * @return bool
  185.          */
  186.         public function transaction_supported(): bool {
  187.                 return true;
  188.         }
  189.  
  190.         /**
  191.          * @return int
  192.          */
  193.         public function transaction_level(): int {
  194.                 return $this->intransaction ? 1 : 0;
  195.         }
  196.  
  197.         /**
  198.          * @return void
  199.          * @throws OIDplusException
  200.          */
  201.         public function transaction_begin()/*: void*/ {
  202.                 if ($this->intransaction) throw new OIDplusException(_L('Nested transactions are not supported by this database plugin.'));
  203.                 if (sqlsrv_begin_transaction($this->conn)) $this->intransaction = true;
  204.         }
  205.  
  206.         /**
  207.          * @return void
  208.          */
  209.         public function transaction_commit()/*: void*/ {
  210.                 if (sqlsrv_commit($this->conn)) $this->intransaction = false;
  211.         }
  212.  
  213.         /**
  214.          * @return void
  215.          */
  216.         public function transaction_rollback()/*: void*/ {
  217.                 if (sqlsrv_rollback($this->conn)) $this->intransaction = false;
  218.         }
  219.  
  220.         /**
  221.          * @param bool $mustExist
  222.          * @return OIDplusSqlSlangPlugin|null
  223.          * @throws OIDplusConfigInitializationException
  224.          */
  225.         protected function doGetSlang(bool $mustExist=true)/*: ?OIDplusSqlSlangPlugin*/ {
  226.                 $slang = OIDplus::getSqlSlangPlugin('mssql');
  227.                 if (is_null($slang)) {
  228.                         throw new OIDplusConfigInitializationException(_L('SQL-Slang plugin "%1" is missing. Please check if it exists in the directory "plugin/sqlSlang". If it is not existing, please recover it from an GIT/SVN snapshot or OIDplus archive file.','mssql'));
  229.                 }
  230.                 return $slang;
  231.         }
  232.  
  233.         /**
  234.          * @return array
  235.          */
  236.         public function getExtendedInfo(): array {
  237.                 $servername = OIDplus::baseConfig()->getValue('SQLSRV_SERVER',   'localhost\oidplus');
  238.                 $username   = OIDplus::baseConfig()->getValue('SQLSRV_USERNAME', '');
  239.                 $password   = OIDplus::baseConfig()->getValue('SQLSRV_PASSWORD', '');
  240.                 $database   = OIDplus::baseConfig()->getValue('SQLSRV_DATABASE', 'oidplus');
  241.                 $options    = OIDplus::baseConfig()->getValue('SQLSRV_OPTIONS',  array());
  242.  
  243.                 $ary_info = array(
  244.                         _L('Hostname') => $servername,
  245.                         _L('Username') => $username,
  246.                         _L('Password') => $password != '' ? '('._L('redacted').')' : '',
  247.                         _L('Database') => $database
  248.                 );
  249.                 foreach ($options as $name => $val) {
  250.                         $ary_info[_L('Option %1',$name)] = '"'.$val.'"';
  251.                 }
  252.                 return $ary_info;
  253.         }
  254. }
  255.