Subversion Repositories oidplus

Rev

Rev 1461 | 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. abstract class OIDplusQueryResult extends OIDplusBaseClass {
  27.  
  28.         /**
  29.          * @return bool
  30.          */
  31.         abstract public function containsResultSet(): bool;
  32.  
  33.         /**
  34.          * @return int
  35.          */
  36.         abstract protected function do_num_rows(): int;
  37.  
  38.         /**
  39.          * @var array|null
  40.          */
  41.         protected $prefetchedArray = null;
  42.  
  43.         /**
  44.          * @var int
  45.          */
  46.         protected $countAlreadyFetched = 0;
  47.  
  48.         /**
  49.          * Please override this method if the database driver can perform a "fetch all" in its own way
  50.          *
  51.          * @return void
  52.          * @throws OIDplusConfigInitializationException
  53.          * @throws OIDplusException
  54.          * @throws \ReflectionException
  55.          */
  56.         public function prefetchAll() {
  57.                 if (!is_null($this->prefetchedArray)) return;
  58.                 $pfa = array();
  59.                 while ($row = $this->fetch_array()) {
  60.                         $pfa[] = $row; // you may not edit $this->prefetchedArray at this step, because $this->>fetch_array() checks it
  61.                         $this->countAlreadyFetched--; // because fetch_array() increases $this->countAlreadyFetched, we need to revert it
  62.                 }
  63.                 $this->prefetchedArray = $pfa;
  64.         }
  65.  
  66.         /**
  67.          * @return int
  68.          * @throws OIDplusConfigInitializationException
  69.          * @throws OIDplusException
  70.          */
  71.         public final function num_rows(): int {
  72.                 if (!$this->containsResultSet()) throw new OIDplusException(_L('The query has returned no result set (i.e. it was not a SELECT query)'));
  73.  
  74.                 if (!is_null($this->prefetchedArray)) {
  75.                         return count($this->prefetchedArray);
  76.                 }
  77.  
  78.                 $ret = $this->do_num_rows();
  79.  
  80.                 if ($ret === -1) throw new OIDplusException(_L('The database driver has problems with "%1"','num_rows'));
  81.  
  82.                 return $ret;
  83.         }
  84.  
  85.         /**
  86.          * Plugins can override and extend this method. It post-processes contents of fetch_array() and fetch_object()
  87.          * to fix various issues with database drivers.
  88.          *
  89.          * @param array|object &$ret
  90.          * @return void
  91.          */
  92.         protected function fixFields(&$ret) {
  93.                 // ODBC gives bit(1) as binary, MySQL as integer and PDO as string.
  94.                 // We'll do it like MySQL does, although ODBC semms to be more correct.
  95.                 // We don't put this code into OIDplusQueryResultODBC.class.php, because other
  96.                 // DBMS might do the same - and then we would be prepared.
  97.                 foreach ($ret as &$value) {
  98.                         if ($value === chr(0)) $value = 0;
  99.                         if ($value === chr(1)) $value = 1;
  100.                 }
  101.                 unset($value);
  102.  
  103.                 // Oracle and Firebird returns $ret['VALUE'] because unquoted column-names are always upper-case
  104.                 // We can't quote every single column throughout the whole program, so we use this workaround...
  105.                 if (is_array($ret)) {
  106.                         foreach ($ret as $name => $val) {
  107.                                 $ret[strtolower($name)] = $val;
  108.                                 $ret[strtoupper($name)] = $val;
  109.                         }
  110.                 } else if (is_object($ret)) {
  111.                         foreach ($ret as $name => $val) {
  112.                                 $ret->{strtoupper($name)} = $val;
  113.                                 $ret->{strtolower($name)} = $val;
  114.                         }
  115.                 } else {
  116.                         assert(false);
  117.                 }
  118.         }
  119.  
  120.         /**
  121.          * Please override do_fetch_object(), do_fetch_array(), or both.
  122.          * @return array|null
  123.          */
  124.         protected function do_fetch_array()/*: ?array*/ {
  125.                 assert(false);
  126.                 return null;
  127.         }
  128.  
  129.         /**
  130.          * @return array|null
  131.          * @throws OIDplusConfigInitializationException
  132.          * @throws OIDplusException
  133.          * @throws \ReflectionException
  134.          */
  135.         public final function fetch_array()/*: ?array*/ {
  136.                 if (!$this->containsResultSet()) throw new OIDplusException(_L('The query has returned no result set (i.e. it was not a SELECT query)'));
  137.                 if (!is_null($this->prefetchedArray)) {
  138.                         // Prefetched value exists. Use it.
  139.                         $ary = $this->prefetchedArray[$this->countAlreadyFetched] ?? null;
  140.                 } else {
  141.                         $reflector = new \ReflectionMethod($this, 'do_fetch_array');
  142.                         $isImplemented = ($reflector->getDeclaringClass()->getName() !== self::class);
  143.                         if ($isImplemented) {
  144.                                 // do_fetch_array() is implemented. Use it.
  145.                                 $ary = $this->do_fetch_array();
  146.                         } else {
  147.                                 // Use the implementation of do_fetch_object()
  148.                                 $reflector = new \ReflectionMethod($this, 'do_fetch_object');
  149.                                 $isImplemented = ($reflector->getDeclaringClass()->getName() !== self::class);
  150.                                 if (!$isImplemented) {
  151.                                         throw new OIDplusException(_L("Class %1 is erroneous: At least one fetch-method needs to be overridden", get_class($this)));
  152.                                 }
  153.                                 $obj = $this->do_fetch_object();
  154.                                 $ary = is_null($obj) ? null : stdobj_to_array($obj);
  155.                         }
  156.                 }
  157.                 if (!is_null($ary)) {
  158.                         $this->countAlreadyFetched++;
  159.                         $this->fixFields($ary);
  160.                 }
  161.                 return $ary;
  162.         }
  163.  
  164.         /**
  165.          * Please override do_fetch_object(), do_fetch_array(), or both.
  166.          * @return object|null
  167.          */
  168.         protected function do_fetch_object()/*: ?\stdClass*/ {
  169.                 assert(false);
  170.                 return null;
  171.         }
  172.  
  173.         /**
  174.          * @return object|null
  175.          * @throws OIDplusConfigInitializationException
  176.          * @throws OIDplusException
  177.          * @throws \ReflectionException
  178.          */
  179.         public final function fetch_object()/*: ?\stdClass*/ {
  180.                 if (!$this->containsResultSet()) throw new OIDplusException(_L('The query has returned no result set (i.e. it was not a SELECT query)'));
  181.                 if (!is_null($this->prefetchedArray)) {
  182.                         // Prefetched value exists (as array). Convert and use it.
  183.                         $ary = $this->prefetchedArray[$this->countAlreadyFetched] ?? null;
  184.                         $obj = is_null($ary) ? null : array_to_stdobj($ary);
  185.                 } else {
  186.                         $reflector = new \ReflectionMethod($this, 'do_fetch_object');
  187.                         $isImplemented = ($reflector->getDeclaringClass()->getName() !== self::class);
  188.                         if ($isImplemented) {
  189.                                 // do_fetch_object() is implemented. Use it.
  190.                                 $obj = $this->do_fetch_object();
  191.                         } else {
  192.                                 // Use the implementation of do_fetch_array()
  193.                                 $reflector = new \ReflectionMethod($this, 'do_fetch_array');
  194.                                 $isImplemented = ($reflector->getDeclaringClass()->getName() !== self::class);
  195.                                 if (!$isImplemented) {
  196.                                         throw new OIDplusException(_L("Class %1 is erroneous: At least one fetch-method needs to be overridden", get_class($this)));
  197.                                 }
  198.                                 $ary = $this->do_fetch_array();
  199.                                 $obj = is_null($ary) ? null : array_to_stdobj($ary);
  200.                         }
  201.                 }
  202.                 if (!is_null($obj)) {
  203.                         $this->countAlreadyFetched++;
  204.                         $this->fixFields($obj);
  205.                 }
  206.                 return $obj;
  207.         }
  208.  
  209.         /**
  210.          * The any() function returns true if there is at least one
  211.          * row in the section. By default, num_rows() will be used.
  212.          * Plugins can override this method if they have a possibility
  213.          * of making this functionality more efficient.
  214.          *
  215.          * @return bool
  216.          * @throws OIDplusException
  217.          */
  218.         public function any(): bool {
  219.                 return $this->num_rows() > 0;
  220.         }
  221.  
  222.         /**
  223.          * @param string $dbField
  224.          * @return void
  225.          * @throws OIDplusConfigInitializationException
  226.          * @throws OIDplusException
  227.          * @throws \ReflectionException
  228.          */
  229.         public final function naturalSortByField(string $dbField) { // TODO: Argument asc or desc order
  230.                 if (is_null($this->prefetchedArray)) {
  231.                         $this->prefetchAll();
  232.                 }
  233.  
  234.                 // Sort $this->prefetchedArray by field $dbField
  235.                 natsort_field($this->prefetchedArray, $dbField);
  236.         }
  237. }
  238.