Skip to content

Commit b488c0f

Browse files
tomlankhorsttaylorotwell
authored andcommitted
[5.7] Expose readStream and writeStream to allow cross-filesystem actions (#23755)
* Expose WriteStream and ReadStream * Added test, clean * Removed additional Stream interfaces
1 parent 5eb5083 commit b488c0f

File tree

4 files changed

+107
-1
lines changed

4 files changed

+107
-1
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Illuminate\Contracts\Filesystem;
4+
5+
use Exception;
6+
7+
class FileExistsException extends Exception
8+
{
9+
//
10+
}

src/Illuminate/Contracts/Filesystem/Filesystem.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ public function exists($path);
3636
*/
3737
public function get($path);
3838

39+
/**
40+
* Get a resource to read the file.
41+
*
42+
* @param string $path
43+
* @return resource|null The path resource or null on failure.
44+
*
45+
* @throws FileNotFoundException
46+
*/
47+
public function readStream($path);
48+
3949
/**
4050
* Write the contents of a file.
4151
*
@@ -46,6 +56,19 @@ public function get($path);
4656
*/
4757
public function put($path, $contents, $options = []);
4858

59+
/**
60+
* Write a new file using a stream.
61+
*
62+
* @param string $path
63+
* @param resource $resource
64+
* @param mixed $options
65+
* @return bool
66+
*
67+
* @throws \InvalidArgumentException If $resource is not a file handle.
68+
* @throws FileExistsException
69+
*/
70+
public function writeStream($path, $resource, array $options = []);
71+
4972
/**
5073
* Get the visibility for the given path.
5174
*

src/Illuminate/Filesystem/FilesystemAdapter.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Illuminate\Support\Collection;
1212
use League\Flysystem\AdapterInterface;
1313
use PHPUnit\Framework\Assert as PHPUnit;
14+
use League\Flysystem\FileExistsException;
1415
use League\Flysystem\FilesystemInterface;
1516
use League\Flysystem\AwsS3v3\AwsS3Adapter;
1617
use League\Flysystem\Cached\CachedAdapter;
@@ -20,6 +21,7 @@
2021
use Symfony\Component\HttpFoundation\StreamedResponse;
2122
use Illuminate\Contracts\Filesystem\Cloud as CloudFilesystemContract;
2223
use Illuminate\Contracts\Filesystem\Filesystem as FilesystemContract;
24+
use Illuminate\Contracts\Filesystem\FileExistsException as ContractFileExistsException;
2325
use Illuminate\Contracts\Filesystem\FileNotFoundException as ContractFileNotFoundException;
2426

2527
/**
@@ -132,7 +134,7 @@ public function response($path, $name = null, array $headers = [], $disposition
132134
]);
133135

134136
$response->setCallback(function () use ($path) {
135-
$stream = $this->driver->readStream($path);
137+
$stream = $this->readStream($path);
136138
fpassthru($stream);
137139
fclose($stream);
138140
});
@@ -390,6 +392,32 @@ public function url($path)
390392
}
391393
}
392394

395+
/**
396+
* {@inheritdoc}
397+
*/
398+
public function readStream($path)
399+
{
400+
try {
401+
$resource = $this->driver->readStream($path);
402+
403+
return $resource ? $resource : null;
404+
} catch (FileNotFoundException $e) {
405+
throw new ContractFileNotFoundException($e->getMessage(), $e->getCode(), $e);
406+
}
407+
}
408+
409+
/**
410+
* {@inheritdoc}
411+
*/
412+
public function writeStream($path, $resource, array $options = [])
413+
{
414+
try {
415+
return $this->driver->writeStream($path, $resource, $options);
416+
} catch (FileExistsException $e) {
417+
throw new ContractFileExistsException($e->getMessage(), $e->getCode(), $e);
418+
}
419+
}
420+
393421
/**
394422
* Get the URL for the file at the given path.
395423
*

tests/Filesystem/FilesystemAdapterTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use League\Flysystem\Adapter\Local;
88
use Illuminate\Filesystem\FilesystemAdapter;
99
use Symfony\Component\HttpFoundation\StreamedResponse;
10+
use Illuminate\Contracts\Filesystem\FileExistsException;
1011
use Illuminate\Contracts\Filesystem\FileNotFoundException;
1112

1213
class FilesystemAdapterTest extends TestCase
@@ -145,4 +146,48 @@ public function testMove()
145146
$this->assertFileExists($this->tempDir.'/foo/foo2.txt');
146147
$this->assertEquals($data, file_get_contents($this->tempDir.'/foo/foo2.txt'));
147148
}
149+
150+
public function testStream()
151+
{
152+
$this->filesystem->write('file.txt', $original_content = 'Hello World');
153+
$filesystemAdapter = new FilesystemAdapter($this->filesystem);
154+
$readStream = $filesystemAdapter->readStream('file.txt');
155+
$filesystemAdapter->writeStream('copy.txt', $readStream);
156+
$this->assertEquals($original_content, $filesystemAdapter->get('copy.txt'));
157+
}
158+
159+
public function testStreamBetweenFilesystems()
160+
{
161+
$secondFilesystem = new Filesystem(new Local($this->tempDir.'/second'));
162+
$this->filesystem->write('file.txt', $original_content = 'Hello World');
163+
$filesystemAdapter = new FilesystemAdapter($this->filesystem);
164+
$secondFilesystemAdapter = new FilesystemAdapter($secondFilesystem);
165+
$readStream = $filesystemAdapter->readStream('file.txt');
166+
$secondFilesystemAdapter->writeStream('copy.txt', $readStream);
167+
$this->assertEquals($original_content, $secondFilesystemAdapter->get('copy.txt'));
168+
}
169+
170+
public function testStreamToExistingFileThrows()
171+
{
172+
$this->expectException(FileExistsException::class);
173+
$this->filesystem->write('file.txt', 'Hello World');
174+
$this->filesystem->write('existing.txt', 'Dear Kate');
175+
$filesystemAdapter = new FilesystemAdapter($this->filesystem);
176+
$readStream = $filesystemAdapter->readStream('file.txt');
177+
$filesystemAdapter->writeStream('existing.txt', $readStream);
178+
}
179+
180+
public function testReadStreamNonExistentFileThrows()
181+
{
182+
$this->expectException(FileNotFoundException::class);
183+
$filesystemAdapter = new FilesystemAdapter($this->filesystem);
184+
$filesystemAdapter->readStream('nonexistent.txt');
185+
}
186+
187+
public function testStreamInvalidResourceThrows()
188+
{
189+
$this->expectException(\InvalidArgumentException::class);
190+
$filesystemAdapter = new FilesystemAdapter($this->filesystem);
191+
$filesystemAdapter->writeStream('file.txt', 'foo bar');
192+
}
148193
}

0 commit comments

Comments
 (0)