Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
   1  <?php
   2  
   3  declare(strict_types=1);
   4  
   5  namespace OpenSpout\Reader;
   6  
   7  use OpenSpout\Common\Exception\IOException;
   8  use OpenSpout\Reader\Exception\ReaderException;
   9  use OpenSpout\Reader\Exception\ReaderNotOpenedException;
  10  
  11  /**
  12   * @template T of SheetIteratorInterface
  13   *
  14   * @implements ReaderInterface<T>
  15   */
  16  abstract class AbstractReader implements ReaderInterface
  17  {
  18      /** @var bool Indicates whether the stream is currently open */
  19      private bool $isStreamOpened = false;
  20  
  21      /**
  22       * Prepares the reader to read the given file. It also makes sure
  23       * that the file exists and is readable.
  24       *
  25       * @param string $filePath Path of the file to be read
  26       *
  27       * @throws \OpenSpout\Common\Exception\IOException If the file at the given path does not exist, is not readable or is corrupted
  28       */
  29      public function open(string $filePath): void
  30      {
  31          if ($this->isStreamWrapper($filePath) && (!$this->doesSupportStreamWrapper() || !$this->isSupportedStreamWrapper($filePath))) {
  32              throw new IOException("Could not open {$filePath} for reading! Stream wrapper used is not supported for this type of file.");
  33          }
  34  
  35          if (!$this->isPhpStream($filePath)) {
  36              // we skip the checks if the provided file path points to a PHP stream
  37              if (!file_exists($filePath)) {
  38                  throw new IOException("Could not open {$filePath} for reading! File does not exist.");
  39              }
  40              if (!is_readable($filePath)) {
  41                  throw new IOException("Could not open {$filePath} for reading! File is not readable.");
  42              }
  43          }
  44  
  45          try {
  46              $fileRealPath = $this->getFileRealPath($filePath);
  47              $this->openReader($fileRealPath);
  48              $this->isStreamOpened = true;
  49          } catch (ReaderException $exception) {
  50              throw new IOException(
  51                  "Could not open {$filePath} for reading!",
  52                  0,
  53                  $exception
  54              );
  55          }
  56      }
  57  
  58      /**
  59       * Closes the reader, preventing any additional reading.
  60       */
  61      final public function close(): void
  62      {
  63          if ($this->isStreamOpened) {
  64              $this->closeReader();
  65  
  66              $this->isStreamOpened = false;
  67          }
  68      }
  69  
  70      /**
  71       * Returns whether stream wrappers are supported.
  72       */
  73      abstract protected function doesSupportStreamWrapper(): bool;
  74  
  75      /**
  76       * Opens the file at the given file path to make it ready to be read.
  77       *
  78       * @param string $filePath Path of the file to be read
  79       */
  80      abstract protected function openReader(string $filePath): void;
  81  
  82      /**
  83       * Closes the reader. To be used after reading the file.
  84       */
  85      abstract protected function closeReader(): void;
  86  
  87      final protected function ensureStreamOpened(): void
  88      {
  89          if (!$this->isStreamOpened) {
  90              throw new ReaderNotOpenedException('Reader should be opened first.');
  91          }
  92      }
  93  
  94      /**
  95       * Returns the real path of the given path.
  96       * If the given path is a valid stream wrapper, returns the path unchanged.
  97       */
  98      private function getFileRealPath(string $filePath): string
  99      {
 100          if ($this->isSupportedStreamWrapper($filePath)) {
 101              return $filePath;
 102          }
 103  
 104          // Need to use realpath to fix "Can't open file" on some Windows setup
 105          $realpath = realpath($filePath);
 106          \assert(false !== $realpath);
 107  
 108          return $realpath;
 109      }
 110  
 111      /**
 112       * Returns the scheme of the custom stream wrapper, if the path indicates a stream wrapper is used.
 113       * For example, php://temp => php, s3://path/to/file => s3...
 114       *
 115       * @param string $filePath Path of the file to be read
 116       *
 117       * @return null|string The stream wrapper scheme or NULL if not a stream wrapper
 118       */
 119      private function getStreamWrapperScheme(string $filePath): ?string
 120      {
 121          $streamScheme = null;
 122          if (1 === preg_match('/^(\w+):\/\//', $filePath, $matches)) {
 123              $streamScheme = $matches[1];
 124          }
 125  
 126          return $streamScheme;
 127      }
 128  
 129      /**
 130       * Checks if the given path is an unsupported stream wrapper
 131       * (like local path, php://temp, mystream://foo/bar...).
 132       *
 133       * @param string $filePath Path of the file to be read
 134       *
 135       * @return bool Whether the given path is an unsupported stream wrapper
 136       */
 137      private function isStreamWrapper(string $filePath): bool
 138      {
 139          return null !== $this->getStreamWrapperScheme($filePath);
 140      }
 141  
 142      /**
 143       * Checks if the given path is an supported stream wrapper
 144       * (like php://temp, mystream://foo/bar...).
 145       * If the given path is a local path, returns true.
 146       *
 147       * @param string $filePath Path of the file to be read
 148       *
 149       * @return bool Whether the given path is an supported stream wrapper
 150       */
 151      private function isSupportedStreamWrapper(string $filePath): bool
 152      {
 153          $streamScheme = $this->getStreamWrapperScheme($filePath);
 154  
 155          return null === $streamScheme || \in_array($streamScheme, stream_get_wrappers(), true);
 156      }
 157  
 158      /**
 159       * Checks if a path is a PHP stream (like php://output, php://memory, ...).
 160       *
 161       * @param string $filePath Path of the file to be read
 162       *
 163       * @return bool Whether the given path maps to a PHP stream
 164       */
 165      private function isPhpStream(string $filePath): bool
 166      {
 167          $streamScheme = $this->getStreamWrapperScheme($filePath);
 168  
 169          return 'php' === $streamScheme;
 170      }
 171  }