0

I am using Laravel 8.x for my new project and need support for a legacy Live web application in Zend, which uses the Rackspace CDN to store the files. So, I need to upload the files in Rackspace CDN from the new application in Laravel 8.x I can upload the files on Amazon S3 successfully, but not able to upload them on Rackspace. I tried with league/flysystem-rackspace, but it's not supported in the current Laravel version.

Controller

public function store(Request $request)
{
    $uploadImage = $request->file('file');
    $filename = time().str_replace(' ', '_', 
            $uploadImage->getClientOriginalName());
    $path = $request->file('file')->storePubliclyAs(
        config('app.cdn_dir'),
        $filename,
        'rackspace'
    );
}

config/filesystem

'disks' => [
    'local' => [
        'driver' => 'local',
        'root' => storage_path('app'),
    ],
    'rackspace' => [
      'driver'    => 'rackspace',
      'username'  => env('CDN_USERNAME'),
      'key'       => env('CDN_KEY'),
      'container' => env('CDN_CONTAINER'),
      'endpoint'  => env('CDN_ENDPOINT', 'https://identity.api.rackspacecloud.com/v2.0/'),
      'region'    => 'IAD',
      'url_type'  => 'publicURL',
      'url' => env('CDN_URL'), 
    ],
],

Error

Error: Class 'Symfony\Component\EventDispatcher\Event' not found in file D:\laragon\www\crm\vendor\guzzle\guzzle\src\Guzzle\Common\Event.php on line 10

Karl Hill
  • 12,937
  • 5
  • 58
  • 95

1 Answers1

1

I had the same problem after upgrading to Laravel 8. I managed to get it working based on this pr comment which was when they removed it from Laravel and this draft which made a start on using the newer php-opencloud sdk.

Rackspace Provider:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Storage;

use League\Flysystem\Filesystem;

use App\Services\Rackspace\RackspaceAdapter;

use OpenStack\OpenStack;
use OpenStack\Common\Transport\Utils as TransportUtils;
use OpenStack\Identity\v2\Service;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

class RackspaceServiceProvider extends ServiceProvider  {

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        Storage::extend('rackspace', function ($app, $config) {

            $httpClient = new Client([
                'base_uri' => TransportUtils::normalizeUrl($config['authUrl']),
                'handler'  => HandlerStack::create(),
            ]);

            $options = [
                'authUrl'         => $config['authUrl'],
                'region'          => $config['region'],
                'username'        => $config['username'],
                'password'        => $config['password'],
                'tenantId'        => $config['tenantid'],
                'identityService' => Service::factory($httpClient),
            ];

            $openstack = new OpenStack($options);

            $store = $openstack->objectStoreV1([
                'catalogName' => 'cloudFiles',
            ]);

            $account = $store->getAccount();
            $container = $store->getContainer($config['container']);

            return new Filesystem(
                new RackspaceAdapter($container, $account), $config
            );
        });
    }
}

And the adapter:

<?php

declare(strict_types=1);

namespace App\Services\Rackspace;

use Carbon\Carbon;
use Exception;
use GuzzleHttp\Psr7\Utils;

use League\Flysystem\Adapter\AbstractAdapter;
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
use League\Flysystem\Config;
use League\Flysystem\Util;

use OpenStack\Common\Error\BadResponseError;
use OpenStack\ObjectStore\v1\Models\Account;
use OpenStack\ObjectStore\v1\Models\Container;
use OpenStack\ObjectStore\v1\Models\StorageObject;

use Throwable;

// Based off https://github.com/thephpleague/flysystem-rackspace/pull/26
final class RackspaceAdapter extends AbstractAdapter
{
    use StreamedCopyTrait;
    use NotSupportingVisibilityTrait;

    private $container;

    private $prefix;

    private $account;

    public function __construct(Container $container, Account $account, string $prefix = '')
    {
        $this->setPathPrefix($prefix);
        $this->account = $account;
        $this->container = $container;
    }

    public function getContainer(): Container
    {
        return $this->container;
    }

    protected function getObject(string $path): StorageObject
    {
        $location = $this->applyPathPrefix($path);

        return $this->container->getObject($location);
    }

    protected function getPartialObject(string $path): StorageObject
    {
        $location = $this->applyPathPrefix($path);

        return $this->container->getObject($location);
    }

    public function write($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $headers = $config->get('headers', []);

        $response = $this->container->createObject([
            'name' => $location,
            'content' => $contents,
            'headers' => $headers,
        ]);

        return $this->normalizeObject($response);
    }

    /**
     * {@inheritdoc}
     */
    public function update($path, $contents, Config $config)
    {
        $object = $this->getObject($path);
        $object->setContent($contents);
        $object->setEtag(null);
        $response = $object->update();

        if (!$response->lastModified) {
            return false;
        }

        return $this->normalizeObject($response);
    }

    /**
     * {@inheritdoc}
     */
    public function rename($path, $newpath)
    {
        $object = $this->getObject($path);
        $newlocation = $this->applyPathPrefix($newpath);
        $destination = sprintf('/%s/%s', $this->container->name, ltrim($newlocation, '/'));
        try {
            $object->copy(['destination' => $destination]);
        } catch (Throwable $exception) {
            return false;
        }

        $object->delete();

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function delete($path)
    {
        try {
            $location = $this->applyPathPrefix($path);

            $this->container->getObject($location)->delete();
        } catch (Throwable $exception) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function deleteDir($dirname)
    {
        $location = $this->applyPathPrefix($dirname);
        $objects = $this->container->listObjects(['prefix' => $location]);

        try {
            foreach ($objects as $object) {
                /* @var $object StorageObject */
                $object->delete();
            }
        } catch (Throwable $exception) {
            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function createDir($dirname, Config $config)
    {
        $headers = $config->get('headers', []);
        $headers['Content-Type'] = 'application/directory';
        $extendedConfig = (new Config())->setFallback($config);
        $extendedConfig->set('headers', $headers);

        return $this->write($dirname, '', $extendedConfig);
    }

    /**
     * {@inheritdoc}
     */
    public function writeStream($path, $resource, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $headers = $config->get('headers', []);

        $response = $this->container->createObject([
            'name' => $location,
            'stream' => Utils::streamFor($resource),
            'headers' => $headers,
        ]);

        return $this->normalizeObject($response);
    }

    /**
     * {@inheritdoc}
     */
    public function updateStream($path, $resource, Config $config)
    {
        return $this->update($path, $resource, $config);
    }

    /**
     * {@inheritdoc}
     */
    public function has($path)
    {
        try {
            $location = $this->applyPathPrefix($path);
            $exists = $this->container->objectExists($location);
        } catch (BadResponseError | Exception $exception) {
            return false;
        }

        return $exists;
    }

    /**
     * {@inheritdoc}
     */
    public function read($path)
    {
        $object = $this->getObject($path);
        $data = $this->normalizeObject($object);

        $stream = $object->download();
        $data['contents'] = $stream->read($object->contentLength);
        $stream->close();

        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function readStream($path)
    {
        $object = $this->getObject($path);
        $responseBody = $object->download();
        $responseBody->rewind();

        return ['stream' => $responseBody->detach()];
    }

    /**
     * {@inheritdoc}
     */
    public function listContents($directory = '', $recursive = false)
    {
        $response = [];
        $marker = null;
        $location = $this->applyPathPrefix($directory);

        while (true) {
            $objectList = $this->container->listObjects(['prefix' => $location, 'marker' => $marker]);
            if (null === $objectList->current()) {
                break;
            }

            $response = array_merge($response, iterator_to_array($objectList));
            $marker = end($response)->name;
        }

        return Util::emulateDirectories(array_map([$this, 'normalizeObject'], $response));
    }

    /**
     * {@inheritdoc}
     */
    protected function normalizeObject(StorageObject $object)
    {
        $name = $object->name;
        $name = $this->removePathPrefix($name);
        $mimetype = explode('; ', $object->contentType);

        return [
            'type' => ((in_array('application/directory', $mimetype)) ? 'dir' : 'file'),
            'dirname' => Util::dirname($name),
            'path' => $name,
            'timestamp' => $object->lastModified,
            'mimetype' => reset($mimetype),
            'size' => $object->contentLength,
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getMetadata($path)
    {
        $object = $this->getPartialObject($path);

        return $this->normalizeObject($object);
    }

    /**
     * {@inheritdoc}
     */
    public function getSize($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getMimetype($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * {@inheritdoc}
     */
    public function getTimestamp($path)
    {
        return $this->getMetadata($path);
    }

    /**
     * @param string $path
     */
    public function applyPathPrefix($path): string
    {
        $encodedPath = join('/', array_map('rawurlencode', explode('/', $path)));

        return parent::applyPathPrefix($encodedPath);
    }

    /**
     * Get the URL for the file at the given path.
     *
     * @param  string $path
     * @return string
     */
    protected function getUrl($path)
    {
        return (string) $this->container->getObject($path)->getPublicUri();
    }

    /**
     * Get a temporary URL for the file at the given path.
     *
     * @param  string  $path
     * @param  \DateTimeInterface  $expiration
     * @param  array  $options
     * @return string
     */
    public function getTemporaryUrl($path, $expiration, array $options = [])
    {
        $object = $this->container->getObject($path);

        $url = $object->getPublicUri();
        $expires = Carbon::now()->diffInSeconds($expiration);
        $method = strtoupper($options['method'] ?? 'GET');
        $expiry = time() + (int) $expires;

        // check for proper method
        if ($method != 'GET' && $method != 'PUT') {
            throw new Exception(sprintf(
                'Bad method [%s] for TempUrl; only GET or PUT supported',
                $method
            ));
        }

        if (!($secret = $this->account->getMetadata()['Temp-Url-Key'])) {
            throw new Exception('Cannot produce temporary URL without an account secret.');
        }

        // if ($forcePublicUrl === true) {
        //     $url->setHost($this->getService()->getEndpoint()->getPublicUrl()->getHost());
        // }

        $urlPath = urldecode($url->getPath());
        $body = sprintf("%s\n%d\n%s", $method, $expiry, $urlPath);
        $hash = hash_hmac('sha1', $body, $secret);

        return sprintf('%s?temp_url_sig=%s&temp_url_expires=%d', $url, $hash, $expiry);
    }
}

Rackspace config in filesystems.php

        'rackspace' => [
            'driver'    => 'rackspace',
            'username'  => env('RACKSPACE_USERNAME'),
            'key'       => env('RACKSPACE_KEY'),
            'container' => env('RACKSPACE_CONTAINER'),
            'password'  => env('RACKSPACE_PASSWORD'),
            'authUrl'   => 'https://lon.identity.api.rackspacecloud.com/v2.0/',
            'region'    => env('RACKSPACE_REGION', 'LON'),
            'tenantid'    => env('RACKSPACE_TENANTID', '1'),
            'url_type'  => 'publicURL',
        ],

And then add the provider in app.php.

This is a rough implementation as only creating, deleting, and getting the temp url have been tested.

Authentication works by using Identity v2.0 because Rackspace doesn't support v3 https://php-openstack-sdk.readthedocs.io/en/latest/services/identity/v2/authentication.html

And the temp url method was based on the function in the old repo https://github.com/rackspace/php-opencloud

Rustom
  • 11
  • 5