1
0
mirror of https://github.com/moodle/moodle.git synced 2025-04-24 09:55:33 +02:00

MDL-77958 file: Add psr stream functionality to file API

This change introduces a new get_psr_stream() method to:
* stored_file
* file_system

This allows us to fetch a Psr Stream implementing the
PSR\Http\Message\StreamInterface and pass it into Guzzle, which means
that there is no need to load the entire file content into memory to
serve it.
This commit is contained in:
Andrew Nicols 2023-04-19 08:54:28 +08:00
parent fd8184a295
commit 96a1a0abce
5 changed files with 74 additions and 10 deletions

@ -14,15 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Core file system class definition.
*
* @package core_files
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use Psr\Http\Message\StreamInterface;
/**
* File system class used for low level access to real files in filedir.
@ -631,6 +623,16 @@ abstract class file_system {
}
}
/**
* Get a PSR7 Stream for the specified file which implements the PSR Message StreamInterface.
*
* @param stored_file $file
* @return StreamInterface
*/
public function get_psr_stream(stored_file $file): StreamInterface {
return \GuzzleHttp\Psr7\Utils::streamFor($this->get_content_file_handle($file));
}
/**
* Retrieve the mime information for the specified stored file.
*

@ -23,6 +23,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use Psr\Http\Message\StreamInterface;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/filestorage/file_progress.php');
@ -437,6 +439,18 @@ class stored_file {
return $this->filesystem->get_content_file_handle($this, $type);
}
/**
* Get a read-only PSR-7 stream for this file.
*
* Note: This stream is read-only. If you want to modify the file, create a new file and delete the old one.
* The File API creates immutable files.
*
* @return StreamInterface
*/
public function get_psr_stream(): StreamInterface {
return $this->filesystem->get_psr_stream($this);
}
/**
* Dumps file content to page.
*/

@ -1077,6 +1077,24 @@ class file_system_test extends \advanced_testcase {
$fs->get_content_file_handle($file, -1);
}
/**
* Ensure that get_content_file_handle returns a valid file handle.
*
* @covers ::get_psr_stream
*/
public function test_get_psr_stream(): void {
$file = $this->get_stored_file('');
$fs = $this->get_testable_mock(['get_remote_path_from_storedfile']);
$fs->method('get_remote_path_from_storedfile')
->willReturn(__FILE__);
$stream = $fs->get_psr_stream($file);
$this->assertInstanceOf(\Psr\Http\Message\StreamInterface::class, $stream);
$this->assertEquals(file_get_contents(__FILE__), $stream->getContents());
$this->assertFalse($stream->isWritable());
}
/**
* Test that mimetype_from_hash returns the correct mimetype with
* a file whose filename suggests mimetype.
@ -1248,4 +1266,3 @@ class file_system_test extends \advanced_testcase {
];
}
}

@ -91,4 +91,33 @@ class stored_file_test extends advanced_testcase {
$this->assertEquals(1200, $size['width']);
$this->assertEquals(297, $size['height']);
}
/**
* Ensure that get_content_file_handle returns a valid file handle.
*
* @covers ::get_psr_stream
*/
public function test_get_psr_stream(): void {
global $CFG;
$this->resetAfterTest();
$filename = 'testimage.jpg';
$filepath = $CFG->dirroot . '/lib/filestorage/tests/fixtures/' . $filename;
$filerecord = [
'contextid' => context_system::instance()->id,
'component' => 'core',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => $filename,
];
$fs = get_file_storage();
$file = $fs->create_file_from_pathname($filerecord, $filepath);
$stream = $file->get_psr_stream();
$this->assertInstanceOf(\Psr\Http\Message\StreamInterface::class, $stream);
$this->assertEquals(file_get_contents($filepath), $stream->getContents());
$this->assertFalse($stream->isWritable());
}
}

@ -138,6 +138,8 @@ information provided here is intended especially for developers.
- taskslimit - to limit the number of tasks in one run
- failed - to limit the run to only the tasks that failed in their previous run
Can be mixed, apart from 'id' of course.
* The file_system class now declares a new, optional, `get_psr_stream` function which allows a Stream implementing `\Psr\Http\Message\StreamInterface` to be returned.
A default implementation which uses the existing file handle resource is used by default, but this should be extended by file_system implementations where relevant.
=== 4.1 ===