Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 57 additions & 7 deletions src/Illuminate/Filesystem/AwsS3V3Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Illuminate\Filesystem;

use Aws\S3\S3Client;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Traits\Conditionable;
use League\Flysystem\FilesystemAdapter as FlysystemAdapter;
use League\Flysystem\FilesystemOperator;
Expand All @@ -20,11 +21,6 @@ class AwsS3V3Adapter extends FilesystemAdapter

/**
* Create a new AwsS3V3FilesystemAdapter instance.
*
* @param \League\Flysystem\FilesystemOperator $driver
* @param \League\Flysystem\FilesystemAdapter $adapter
* @param array $config
* @param \Aws\S3\S3Client $client
*/
public function __construct(FilesystemOperator $driver, FlysystemAdapter $adapter, array $config, S3Client $client)
{
Expand Down Expand Up @@ -57,6 +53,62 @@ public function url($path)
);
}

/**
* If a ZIP File is requested from S3 Storage,
* copy it first to local storage
* and then return the local.
*
* @return string|null
*/
public function get($path)
{
if (str_ends_with($path, '.zip')) {
$localDisk = new Filesystem;
$localDisk->put(
$path,
$this->readStream($path)
);

return $localDisk->get($path);
}

return parent::get($path);
}

public function path($path)
{
if (str_ends_with($path, '.zip')) {
$localDisk = new Filesystem;
$localDisk->put(
$path,
$this->readStream($path)
);

return $path;
}

return parent::path($path);
}

public function processFileUsing($path, $closure, ?string $disk = null)
{
$isZip = str_ends_with($path, '.zip');
if ($isZip) {
$localDisk = new Filesystem;
$localDisk->put(
$path,
$this->readStream($path)
);
}
try {
return $closure($path);
} finally {
if ($isZip) {
$localDisk->delete($path);
}
}
}

/**
* Determine if temporary URLs can be generated.
*
Expand All @@ -72,7 +124,6 @@ public function providesTemporaryUrls()
*
* @param string $path
* @param \DateTimeInterface $expiration
* @param array $options
* @return string
*/
public function temporaryUrl($path, $expiration, array $options = [])
Expand Down Expand Up @@ -101,7 +152,6 @@ public function temporaryUrl($path, $expiration, array $options = [])
*
* @param string $path
* @param \DateTimeInterface $expiration
* @param array $options
* @return array
*/
public function temporaryUploadUrl($path, $expiration, array $options = [])
Expand Down
31 changes: 15 additions & 16 deletions src/Illuminate/Filesystem/FilesystemAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
Expand Down Expand Up @@ -93,10 +94,6 @@ class FilesystemAdapter implements CloudFilesystemContract

/**
* Create a new filesystem adapter instance.
*
* @param \League\Flysystem\FilesystemOperator $driver
* @param \League\Flysystem\FilesystemAdapter $adapter
* @param array $config
*/
public function __construct(FilesystemOperator $driver, FlysystemAdapter $adapter, array $config = [])
{
Expand Down Expand Up @@ -314,7 +311,6 @@ public function json($path, $flags = 0)
*
* @param string $path
* @param string|null $name
* @param array $headers
* @param string|null $disposition
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
Expand Down Expand Up @@ -349,10 +345,8 @@ public function response($path, $name = null, array $headers = [], $disposition
/**
* Create a streamed download response for a given file.
*
* @param \Illuminate\Http\Request $request
* @param string $path
* @param string|null $name
* @param array $headers
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function serve(Request $request, $path, $name = null, array $headers = [])
Expand All @@ -367,7 +361,6 @@ public function serve(Request $request, $path, $name = null, array $headers = []
*
* @param string $path
* @param string|null $name
* @param array $headers
* @return \Symfony\Component\HttpFoundation\StreamedResponse
*/
public function download($path, $name = null, array $headers = [])
Expand Down Expand Up @@ -798,7 +791,6 @@ public function providesTemporaryUrls()
*
* @param string $path
* @param \DateTimeInterface $expiration
* @param array $options
* @return string
*
* @throws \RuntimeException
Expand All @@ -823,7 +815,6 @@ public function temporaryUrl($path, $expiration, array $options = [])
*
* @param string $path
* @param \DateTimeInterface $expiration
* @param array $options
* @return array
*
* @throws \RuntimeException
Expand Down Expand Up @@ -1023,7 +1014,6 @@ protected function parseVisibility($visibility)
/**
* Define a custom callback that generates file download responses.
*
* @param \Closure $callback
* @return void
*/
public function serveUsing(Closure $callback)
Expand All @@ -1034,7 +1024,6 @@ public function serveUsing(Closure $callback)
/**
* Define a custom temporary URL builder callback.
*
* @param \Closure $callback
* @return void
*/
public function buildTemporaryUrlsUsing(Closure $callback)
Expand All @@ -1043,9 +1032,21 @@ public function buildTemporaryUrlsUsing(Closure $callback)
}

/**
* Determine if Flysystem exceptions should be thrown.
* This method accepts a closure to process the given Path.
*
* @return bool
* @return mixed
*/
public function processFileUsing(string $path, Closure $closure, ?string $disk = null)
{
if ($disk) {
return Storage::disk($disk)->processFileUsing($path, $closure);
}

return $closure($this->path($path));
}

/**
* Determine if Flysystem exceptions should be thrown.
*/
protected function throwsExceptions(): bool
{
Expand All @@ -1067,8 +1068,6 @@ protected function report($exception)

/**
* Determine if Flysystem exceptions should be reported.
*
* @return bool
*/
protected function shouldReport(): bool
{
Expand Down
134 changes: 134 additions & 0 deletions tests/Filesystem/AwsS3V3AdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

namespace Illuminate\Tests\Filesystem;

use Illuminate\Filesystem\AwsS3V3Adapter;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Foundation\Application;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Mockery as m;
use PHPUnit\Framework\TestCase;

class AwsS3V3AdapterTest extends TestCase
{
private $tempDir;

private $filesystem;

private $adapter;

protected function setUp(): void
{
$this->tempDir = __DIR__.'/tmp';
$this->filesystem = new Filesystem(
$this->adapter = new LocalFilesystemAdapter($this->tempDir)
);
}

protected function tearDown(): void
{
$filesystem = new Filesystem(
$this->adapter = new LocalFilesystemAdapter(dirname($this->tempDir))
);
$filesystem->deleteDirectory(basename($this->tempDir));
m::close();

unset($this->tempDir, $this->filesystem, $this->adapter);
}

public function test_process_file_using_callback_returns_expected_values_for_s3_adapter_with_zip_file()
{
$filesystem = new FilesystemManager(new Application);

$filesystemAdapter = $filesystem->createS3Driver([
'region' => 'us-west-1',
'bucket' => 'laravel',
]);

$receivedPath = null;

$result = $filesystemAdapter->processFileUsing('archive.zip', function ($path) use (&$receivedPath) {
$receivedPath = $path;

return 'processed-zip';
});

$this->assertSame('processed-zip', $result);

$this->assertNotNull($receivedPath);
$this->assertStringEndsWith('archive.zip', $receivedPath);
}

public function test_get_copies_zip_file_from_s3_to_local_and_returns_contents()
{
/** @var AwsS3V3Adapter|m\MockInterface $adapter */
$adapter = m::mock(AwsS3V3Adapter::class)->makePartial();

$stream = fopen('php://temp', 'r+');
fwrite($stream, 'zip-contents');
rewind($stream);

$adapter->shouldReceive('readStream')
->once()
->with('archive.zip')
->andReturn($stream);

$adapter->shouldReceive('get')->passthru()->byDefault();

$result = $adapter->get('archive.zip');

$this->assertSame('zip-contents', $result);
}

public function test_get_falls_back_to_parent_get_for_non_zip_files()
{
/** @var AwsS3V3Adapter|m\MockInterface $adapter */
$filesystemAdapter = new FilesystemAdapter($this->filesystem, $this->adapter);
$adapter = $this->createConfiguredMock(AwsS3V3Adapter::class, [
'get' => $filesystemAdapter->get('file.txt'),
]);

$this->assertNull($adapter->get('file.txt'));
}

public function test_path_copies_zip_file_from_s3_to_local_and_returns_original_path()
{
/** @var AwsS3V3Adapter|m\MockInterface $adapter */
$adapter = m::mock(AwsS3V3Adapter::class)->makePartial();

$stream = fopen('php://temp', 'r+');
fwrite($stream, 'zip-contents');
rewind($stream);

$adapter->shouldReceive('readStream')
->once()
->with('archive.zip')
->andReturn($stream);

$adapter->shouldReceive('path')->passthru()->byDefault();

$result = $adapter->path('archive.zip');

$this->assertSame('archive.zip', $result);
}

public function test_path_falls_back_to_parent_path_for_non_zip_files()
{
$filesystem = new Filesystem(
$this->adapter = new LocalFilesystemAdapter($this->tempDir)
);

$filesystemAdapter = new FilesystemAdapter($filesystem, $this->adapter, [
'root' => $this->tempDir.DIRECTORY_SEPARATOR,
]);

$this->filesystem->write('file.txt', 'Hello World');

$this->assertEquals(
$this->tempDir.DIRECTORY_SEPARATOR.'file.txt',
$filesystemAdapter->path('file.txt')
);
}
}
Loading
Loading