2

After updating to Laravel 9.14 with PHP 8.1.4 then streaming has broke that was based on this answer https://stackoverflow.com/a/52598361/6825499.

I can see that it is because of a

Call to undefined method League\Flysystem\AwsS3V3\AwsS3V3Adapter::getClient()

So it seems to have been removed in the newest version from league/flysystem-aws-s3-v3 (3.0.13)

I did find a reference to this SO post which tries to explain there is a workaround now: Get S3Client from storage facade in Laravel 9

This is though too complex for me to understand.

Does anyone know what can be done?

Scorekaj22
  • 65
  • 9

2 Answers2

1

You need to update the PHP version on your server to the latest one. It sounds like the server is still on the PHP 7.x version and that's the issue.

  • Hi @martin gray, welcome to Stack Overflow. Thanks for answering! You might be able to improve your answer further by explaining *how* you know it's a bad PHP version. This might be a link to docs or to another Q&A, or it could be an explanation of some relevant error message. – ariels May 30 '22 at 13:54
  • @Martin Gray - Thank you for trying to answer. The server is running PHP 8.1.4 as described above so this is not the case. I am adamant on it having to do with the updates to the league/Flysystem. – Scorekaj22 May 30 '22 at 17:24
0

After researching quite a bit I realized that the issue came down to updates described in Laravel document page about upgrading.

I ended up altering the code a bit where I use environment variables to fill out what was before provieded by the adapter in the code.

In the latest version of the flysystem,there is no longer access to the client via the adapter. Due to mentioned reason, the service has been broken.

For a fully working class then you can use below code for laravel version 9

    <?php
    namespace App\Http;
    
    use Exception;
    use Illuminate\Filesystem\FilesystemAdapter;
    use Illuminate\Http\Response;
    use Illuminate\Support\Arr;
    use Illuminate\Support\Str;
    use League\Flysystem\Filesystem;
    use Storage;
    use Symfony\Component\HttpFoundation\StreamedResponse;
    
    class S3FileStream
    {
        /**
         * Name of adapter
         *
         * @var string
         */
        private $adapterName;
    
        /**
         * Storage disk
         *
         * @var FilesystemAdapter
         */
        private $disk;
    
        /**
         * @var int file end byte
         */
        private $end;
    
        /**
         * @var string
         */
        private $filePath;
    
        /**
         * Human-known filename
         *
         * @var string|null
         */
        private $humanName;
    
        /**
         * @var bool storing if request is a range (or a full file)
         */
        private $isRange = false;
    
        /**
         * @var int|null length of bytes requested
         */
        private $length = null;
    
        /**
         * @var array
         */
        private $returnHeaders = [];
    
        /**
         * @var int file size
         */
        private $size;
    
    /**
         * @var string bucket name
         */
        private $bucket;
    
        /**
         * @var int start byte
         */
        private $start;
    
        /**
         * S3FileStream constructor.
         * @param string $filePath
         * @param string $adapter
         * @param string $humanName
         */
        public function __construct(string $filePath, string $adapter = 's3', ?string $humanName = null)
        {
            $options = [
        'region'            => env("AWS_DEFAULT_REGION"),
        'version'           => 'latest'
    ];
            $this->filePath    = $filePath;
            $this->adapterName = $adapter;
            $this->disk        = Storage::disk($this->adapterName);
            $this->client     = new \Aws\S3\S3Client($options);
            $this->humanName   = $humanName;
            $this->bucket      = env("AWS_BUCKET");
            //Set to zero until setHeadersAndStream is called
            $this->start = 0;
            $this->size  = 0;
            $this->end   = 0;
        }
    
        /**
         * Output file to client.
         */
        public function output()
        {
            return $this->setHeadersAndStream();
        }
    
        /**
         * Output headers to client.
         * @return Response|StreamedResponse
         */
        protected function setHeadersAndStream()
        {
            if (!$this->disk->exists($this->filePath)) {
                report(new Exception('S3 File Not Found in S3FileStream - ' . $this->adapterName . ' - ' . $this->disk->path($this->filePath)));
                return response('File Not Found', 404);
            }
    
            $this->start   = 0;
            $this->size    = $this->disk->size($this->filePath);
            $this->end     = $this->size - 1;
            $this->length  = $this->size;
            $this->isRange = false;
    
            //Set headers
            $this->returnHeaders = [
                'Last-Modified'       => $this->disk->lastModified($this->filePath),
                'Accept-Ranges'       => 'bytes',
                'Content-Type'        => $this->disk->mimeType($this->filePath),
                'Content-Disposition' => 'inline; filename=' . ($this->humanName ?? basename($this->filePath) . '.' . Arr::last(explode('.', $this->filePath))),
                'Content-Length'      => $this->length,
            ];
    
            //Handle ranges here
            if (!is_null(request()->server('HTTP_RANGE'))) {
                $cStart = $this->start;
                $cEnd   = $this->end;
    
                $range = Str::after(request()->server('HTTP_RANGE'), '=');
                if (strpos($range, ',') !== false) {
                    return response('416 Requested Range Not Satisfiable', 416, [
                        'Content-Range' => 'bytes */' . $this->size,
                    ]);
                }
                if (substr($range, 0, 1) == '-') {
                    $cStart = $this->size - intval(substr($range, 1)) - 1;
                } else {
                    $range  = explode('-', $range);
                    $cStart = intval($range[0]);
    
                    $cEnd = (isset($range[1]) && is_numeric($range[1])) ? intval($range[1]) : $cEnd;
                }
    
                $cEnd = min($cEnd, $this->size - 1);
                if ($cStart > $cEnd || $cStart > $this->size - 1) {
                    return response('416 Requested Range Not Satisfiable', 416, [
                        'Content-Range' => 'bytes */' . $this->size,
                    ]);
                }
    
                $this->start                           = intval($cStart);
                $this->end                             = intval($cEnd);
                $this->length                          = min($this->end - $this->start + 1, $this->size);
                $this->returnHeaders['Content-Length'] = $this->length;
                $this->returnHeaders['Content-Range']  = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
                $this->isRange                         = true;
            }
    
            return $this->stream();
        }
    
        /**
         * Stream file to client.
         * @throws Exception
         * @return StreamedResponse
         */
        protected function stream(): StreamedResponse
        {
            $this->client->registerStreamWrapper();
            // Create a stream context to allow seeking
            $context = stream_context_create([
                's3' => [
                    'seekable' => true,
                ],
            ]);
            // Open a stream in read-only mode
            if (!($stream = fopen("s3://{$this->bucket}/{$this->filePath}", 'rb', false, $context))) {
                throw new Exception('Could not open stream for reading export [' . $this->filePath . ']');
            }
            if (isset($this->start) && $this->start > 0) {
                fseek($stream, $this->start, SEEK_SET);
            }
    
            $remainingBytes = $this->length ?? $this->size;
            $chunkSize      = 100;
    
            $video = response()->stream(
                function () use ($stream, $remainingBytes, $chunkSize) {
                    while (!feof($stream) && $remainingBytes > 0) {
                        $toGrab = min($chunkSize, $remainingBytes);
                        echo fread($stream, $toGrab);
                        $remainingBytes -= $toGrab;
                        flush();
                    }
                    fclose($stream);
                },
                ($this->isRange ? 206 : 200),
                $this->returnHeaders
            );
    
            return $video;
        }
    }
Omid Reza Heidari
  • 658
  • 12
  • 27
Scorekaj22
  • 65
  • 9