0

I am trying to integrate some stream wrappers from Gaufrette into Drupal. I was able to make an amazon S3 wrapper before by providing tagged service and extending the official Amazon S3 stream wrapper so I know it can be done.

With Gaufrette though, this is not that easy because it internally namespaces the wrapper directly in the uri as gaufrette://[wrapper scheme]/somefile.

I tried to use the tagged service as stream wrapper and delegate calls to gaufrette's filesystem(google cloud storage in this case) but I think the way the gaufrette's Filesystem is mapped via FilesystemMap is not working properly with my solution.

So Instead of wasting too much additional time I am thinking that I'd rather register gaufrette's stream wrapper directly instead of wrapping it just to please Drupal. But at the same time I want to have this stream wrapper listed in the UI so I somehow have to implement Drupal's stream wrapper interface which extends the php wrapper interface which makes things complicated(because these "external" stream wrappers don't always implement all required methods, or have all the arguments or required additional internal functionality).

I was thinking of re-registering the stream wrapper class in my stream wrapper constructor to provide gaufrette's class but I don't think that is a working solution even in theory.

Has anyone tried something like this?

https://knplabs.github.io/Gaufrette/streaming.html

  • You mentioned that you previously made a stream wrapper for S3. Would you mind sharing this if you still have it please? – Jimmyb_1991 May 18 '18 at 0:03
  • @Jimmyb_1991probably github.com/ivanjaros/original-merqio-store/tree/… but the Google one is just on another level if you're interested. – user21641 May 18 '18 at 9:25
0

This is not yet committed into the Drupal Examples Project for Drupal 8, but the current code I wrote for Stream Wrapper Example will do what you need.

Your custom stream wrapper implementation needs to implement the InboundPathProcessorInterface, similar to the way that Drupal's public: and private: wrappers do. The sample code (done for the File Example originally, but split off by Mile23) will show you how to do this.

0

OK, so this is not an universal answer but applies to the Gaufrette. I solved the internal namespacing issue by overriding the createStream method. Since this method is called only when the stream is being open it covers the whole logic.

This is my current code:

<?php

namespace Drupal\mymodule\StreamWrapper;

use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Gaufrette\StreamWrapper AS GaufretteStreamWrapper;

/**
 * Class GaufretteStreamWrapperBase
 *
 * Provides base class for integrating Gaufrette's stream wrapper
 * file system adapters with Drupal's stream wrapper framework.
 *
 * @todo refactor this to be universal(gaufrette-less) bridge.
 */
abstract class GaufretteStreamWrapperBase extends GaufretteStreamWrapper implements StreamWrapperInterface {

  /**
   * Instance URI (stream).
   *
   * A stream is referenced as "scheme://target".
   *
   * @var string
   */
  protected $uri;

  /**
   * The Gaufrette filesystem(s), keyed by the scheme.
   *
   * @var \Gaufrette\Filesystem[]
   */
  public static $filesystem = [];

  /**
   * Constructs a new GaufretteStreamWrapperBase object.
   */
  public function __construct() {
    if (empty($this->getFilesystem())) {
      $this->setFilesystem();
    }
  }

  /**
   * This method is responsible for instantiating the filesystem.
   *
   * @return \Gaufrette\Filesystem
   */
  abstract protected function initializeFilesystem();

  /**
   * The stream scheme name that this class represents.
   *
   * @return string
   */
  abstract public function getScheme();

  /**
   * Sets the Gaufrette filesystem for the active scheme.
   */
  protected function setFilesystem() {
    static::$filesystem[$this->getScheme()] = $this->initializeFilesystem();
  }

  /**
   * Returns the Gaufrette filesystem for active scheme.
   *
   * @return \Gaufrette\Filesystem
   */
  public function getFilesystem() {
    return isset(static::$filesystem[$this->getScheme()]) ? static::$filesystem[$this->getScheme()] : NULL;
  }

  /**
   * An optional prefix to be internally set to the URI.
   * For example scheme 's3://foobar.txt' will result in
   * 's3://[BUCKET NAME]/foobar.txt' because S3 uses buckets.
   * If this method would provide value of 'my_site' then the real
   * URI would be 's3://[BUCKET NAME]/my_site/foobar.txt'.
   *
   * @return string
   */
  public function getPrefix() {
    return '';
  }

  /**
   * {@inheritdoc}
   */
  protected function createStream($path) {
    return $this->getFilesystem()->createStream($path);
  }


  /****************************************************************************
   * StreamWrapperInterface methods.
   ***************************************************************************/

  /**
   * {@inheritdoc}
   */
  public static function getType() {
    return StreamWrapperInterface::NORMAL;
  }

  /**
   * {@inheritdoc}
   */
  public function setUri($uri) {
    $this->uri = $uri;
  }

  /**
   * {@inheritdoc}
   */
  public function getUri() {
    return $this->uri;
  }

  /**
   * {@inheritdoc}
   */
  public function dirname($uri = NULL) {
    list($scheme) = explode('://', $uri, 2);
    $target = file_uri_target($uri);
    $dirname = dirname($target);

    if ($dirname == '.') {
      $dirname = '';
    }

    return $scheme . '://' . $dirname;
  }


  /****************************************************************************
   * PhpStreamWrapperInterface methods
   ***************************************************************************/

  /**
   * Helper method that will delegate method calls to parent
   * if they are supported. Otherwise it will return FALSE.
   *
   * Since declaring the methods in annotation and using the __call()
   * magic method is not working with interfaces we have to process
   * each method manually.
   *
   * @param string $method
   *   The called class method name.
   * @param array $arguments
   *   Arguments provided to the caller.
   *
   * @return bool|mixed
   *   Returns the return value from existing parent method
   *   or FALSE if the method does not exist.
   */
  protected function delegate($method, $arguments) {
    $method = 'parent::' . $method;
    if (is_callable($method)) {
      return call_user_func_array($method, $arguments);
    } else {
      return FALSE;
    }
  }

  /**
   * {@inheritdoc}
   */
  public function dir_closedir() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function dir_opendir($path, $options) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function dir_readdir() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function dir_rewinddir() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function mkdir($path, $mode, $options) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function rename($path_from, $path_to) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function rmdir($path, $options) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_cast($cast_as) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_close() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_eof() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_flush() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_lock($operation) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_metadata($path, $option, $value) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   *
   * The \Gaufrette\StreamWrapper::stream_open and
   * \Drupal\Core\StreamWrapper\PhpStreamWrapperInterface::stream_open
   * have incompatible arguments so we have to provide
   * default values for the mismatched ones.
   */
  public function stream_open($path, $mode, $options = NULL, &$opened_path = NULL) {
    // Only the first two arguments are supported,
    // so there is no need to take care of the reference.
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_read($count) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_seek($offset, $whence = SEEK_SET) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_set_option($option, $arg1, $arg2) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_stat() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_tell() {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_truncate($new_size) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function stream_write($data) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function unlink($path) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

  /**
   * {@inheritdoc}
   */
  public function url_stat($path, $flags) {
    return $this->delegate(__FUNCTION__, func_get_args());
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy