ricwein/filesystem

文件系统抽象层

5.0-beta1 2023-10-27 18:57 UTC

README

这个库提供了一个文件系统抽象层。

use ricwein\FileSystem\Exceptions\FilesystemException;
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

try {

    $file = new File(new Storage\Disk(__DIR__, '/test', '/dir2', 'test.json'));
    $alt = new File(new Storage\Memory('{"some": "content"}'));

    // read file into output-buffer
    if ($file->isReadable()) {
        header('Content-Type: ' . $file->getType(true));
        echo $file->read();
    } else {
        header('Content-Type: ' . $alt->getType(true));
        echo $alt->read();
    }

} catch (FileSystemException $e) {
    http_response_code(500);
    echo json_encode(['errors' => [
        'status' => $e->getCode(),
        'title' => 'something went wrong',
        'detail' => $e->getMessage(),
    ]]);
}

安装

composer require ricwein/filesystem

概述

让我们先简要了解一下支持的文件系统类和抽象。

所有类都使用根命名空间 ricwein\FileSystem

对象类型

文件用 File 对象表示,目录用 Directory 对象表示。

存储

访问对象(文件/目录)的内容被抽象为 Storage。文件可以是

  • 一个位于本地文件系统中的 Disk 文件/目录(Storage\Disk
  • 一个 Memory 文件,它仅临时存在于内存中(Storage\Memory
  • 一个 Stream,它指向一个文件或资源(Storage\Stream
  • 一个抽象的 Flysystem 文件(Storage\Flysystem

所有存储类型都必须扩展抽象基类 Filesystem\Storage

警告:由于存储对象是可变的,并且 PHP 在将类对象作为引用传递到函数(构造函数)时自动处理,因此当存储在 FileSystem-对象之间回收时,强烈建议使用 clone 关键字。不要

use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Storage;

$originalDir = new Directory(new Storage\Disk(__DIR__));
$copyDir = new Directory($originalDir->storage());

$copyDir->cd('test'); // will also change $originalDir path!

use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Storage;

$originalDir = new Directory(new Storage\Disk(__DIR__));
$copyDir = new Directory(clone $originalDir->storage());

$copyDir->cd('test'); // $originalDir will stay in __DIR__

异常

访问文件/目录属性可能会导致抛出异常。所有异常都实现了 Exceptions\FileSystemException 接口。

用法:文件

所有 FileSystem 基类都必须使用存储进行初始化。

方法

从本地文件系统

use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$file = new File(new Storage\Disk(__DIR__, 'test.txt'));
$content = $file->read();

或从内存文件

use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$file = new File(new Storage\Memory('some content'));
$content = $file->read();

或从流中

use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$file = new File(new Storage\Stream(fopen('php://output', 'wb')));
$content = $file->write('content');

或从 Flysystem 对象中

use League\Flysystem\Local\LocalFilesystemAdapter;
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$file = new File(new Storage\Flysystem(new LocalFilesystemAdapter(__DIR__), 'test.txt'));
$content = $file->read();

打开并读取文件

类似文件,目录也必须使用存储进行初始化。

方法

检查目录是否可读

use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Storage;

$dir = new Directory(new Storage\Disk(__DIR__));
var_dump($dir->isReadable());

列出目录中的所有文件

use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Storage;

$hashes = [];
$dir = new Directory(new Storage\Disk(__DIR__));

foreach($dir->list(true)->files() as $file) {
    $hashes[$file->getPath()->getFilename()] = $file->getHash();
}

安全性

使用此文件系统层还可以为使用用户定义的文件路径提供一定的安全性。在访问文件属性或内容之前,只会检查所谓的 约束

约束

约束在初始化 FileDirectory 对象时定义,并存储在内部 Storage 对象中。这允许约束继承,如果从现有的 FileSystem-对象访问新的 FileSystem-对象。示例

use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Helper\Constraint;
use ricwein\FileSystem\Storage;

$dir = new Directory(new Storage\Disk(__DIR__), Constraint::STRICT);
$file = $dir->file($_GET['filename']);

在这个例子中,$file 对象与 约束(继承的)和 safepath$dir 共享 - 允许从用户定义的参数安全地访问 $dir 中的文件。因此,防止路径遍历。

以下约束设置为默认值(作为 Constraint::STRICT 的一部分),但可以用 File($storage, $constraints)Directory($storage, $constraints) 构造函数的第二个参数覆盖

  • Constraint::IN_OPEN_BASEDIR => 路径必须在 open_basedir php-ini 路径内,这允许在遇到 PHP 核心错误之前抛出异常
  • Constraint::DISALLOW_LINK => 路径不能是(符号)链接
  • Constraint::IN_SAFEPATH => 如果文件/目录路径由多个组件(参数)组成,则生成的文件/目录目标必须位于第一个路径组件(称为 safepath)内
use ricwein\FileSystem\File;
use ricwein\FileSystem\Helper\Constraint;
use ricwein\FileSystem\Storage;

// let's assume $_GET['file'] == '/../file.txt'

// path concatenated as a single string
// this runs fine but is HIGHLY UNRECOMMENDED
$file = new File(new Storage\Disk(__DIR__ . $_GET['file']));

// path is passed as single parameters (comma instead of dot!)
// this throws an error since the resulting path is not within the safepath (__DIR__)
$file = new File(new Storage\Disk(__DIR__, $_GET['file']));

// however: disabling the safepath-constraint would also allow path traversal attacks:
$file = new File(new Storage\Disk(__DIR__, $_GET['file']), Constraint::STRICT & ~Constraint::IN_SAFEPATH);

扩展

目录扩展

  • Directory\Command:允许在给定的目录中运行 shell 命令。
use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Helper\Constraint;
use ricwein\FileSystem\Storage;

$git = new Directory\Command(new Storage\Disk(__DIR__), Constraint::STRICT, ['/usr/local/bin/git', '/usr/bin/git']);
$ref = $git->execSafe('rev-parse HEAD');

文件扩展

  • File\Image:允许基于 imagemagickgd(默认)进行图像处理。需要 Intervention\Image 包。

注意:所有图像文件处理都会直接修改原始文件!

use Intervention\Image\Image;
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$image = new File\Image(new Storage\Disk('test.png'));
$image->resizeToFit(1024, 1024);
$image->compress(1048576); // iterative process to reduce filesize to be less than given filesize (1MB) by reducing the jpg-quality
// $image->encode('jpg');
$image->edit(function (Image $image): Image {
    // add advanced image-manipulation here
    [...]
    return $image;
});
  • File\Zip:允许基本的 zip 操作,如创建新存档或提取现有存档。
use ricwein\FileSystem\Directory;
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$zip = new File\Zip(new Storage\Disk('archive.zip'));

// create zip file
$zip->add(new File(new Storage\Disk('file.json'))); // or $zip->addFile(...)
$zip->add(new File(new Storage\Memory('some file-content')), 'anotherfile.txt'); // or $zip->addFile(...)
$zip->add(new Directory(new Storage\Disk(__DIR__, 'data-dir'))); // or $zip->addDirectory(...)
$zip->commit();

// extract zip file
$extractDir = $zip->extractTo(new Storage\Disk\Temp);
  • 文件\SSLCertificate:访问一些基本的x509 SSL证书信息,可以是来自
    • 证书文件(通常是 .crt
    • 服务器(通过URL),如果它正在提供基于SSL的协议如 HTTPS
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$cert = new File\SSLCertificate(new Storage\File('certs/domain.crt'));
// OR
$cert = new File\SSLCertificate(new Storage\Memory('ssl://domain.com:443')); // or for HTTPS simply: 'domain.com' 

// check if cert it currently valid (by timestamps)
$isValid = $cert->isValid();
// check if cert is also valid for a given host
$isValidForHost = $cert->isValidFor('subdomain.domain.com');

// show some certificate details
print_r([
    'issuer' => $cert->getIssuer(),
    'subject' =>  $cert->getValidDomains(),
    'validFrom' => $cert->validFrom()->format('d.m.Y H:i:s'),
    'validTo' => $cert->validTo()->format('d.m.Y H:i:s'),
]);

存储扩展

  • 磁盘\当前:使用当前工作目录(getcwd())作为安全路径。与 Directory\Command 结合使用时对cli脚本很有用。
use ricwein\FileSystem\File;
use ricwein\FileSystem\Helper\Constraint;
use ricwein\FileSystem\Storage;

$current = new File(new Storage\Disk(getcwd(), 'file.json'), Constraint::STRICT & ~Constraint::IN_SAFEPATH);
// is the same as:
$current = new File(new Storage\Disk\Current('file.json'));
use ricwein\FileSystem\Directory;
use ricwein\FileSystem\Helper\Constraint;
use ricwein\FileSystem\Storage;

$git = new Directory\Command(new Storage\Disk\Current, Constraint::STRICT, ['/usr/local/bin/git', '/usr/bin/git']);

if (!$git->execSafe('pull $branch', ['branch' => 'develop'])) {
    echo 'failed to execute: ' . $git->getLastCommand() . PHP_EOL;
}

exit($git->lastExitCode());
  • 磁盘\临时:使用系统临时目录创建临时文件/目录。对象实例释放后文件将自动删除!
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$temp = new File(new Storage\Disk\Temp);
$temp->write('test');
$temp->read();
$temp = null; // will delete the temp-file again!
  • 磁盘\上传:通过PHP的本地 is_uploaded_file()move_uploaded_file() 函数提供安全且方便的上传文件访问。
use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$uploaded = new File(new Storage\Disk\Uploaded($_FILES['file']));
$file = $uploaded->moveTo(new Storage\Disk(__DIR__, 'uploads'));
  • 内存\资源:在构造时将资源内容读入 内存。之后可以关闭资源。

注意:通常直接使用 Storage\Stream 是更好的选择!

use ricwein\FileSystem\File;
use ricwein\FileSystem\Storage;

$resource = fopen('test.json', 'rb');
$file = new File(new Storage\Memory\Resource($resource));
fclose($resource);
$content = $file->read();