ryunosuke / stream-wrapper
Requires
- php: >=7.4
Requires (Dev)
- aws/aws-sdk-php: 3.*
- phpunit/phpunit: 9.*
- predis/predis: 2.*
- ryunosuke/phpunit-extension: 3.*
- symfony/mailer: 5.*
This package is auto-updated.
Last update: 2024-09-09 12:05:06 UTC
README
描述
这是一个用于规范php流包装器的包。
php的流包装器功能强大且灵活,但使用起来困难,即使阅读了文档,也很难理解其实际实现方法。此外,实现时接口被切断,需要一边阅读文档一边在尝试中不断尝试错误地实现。另外,有些方法(看似没有关系)实际上使用相同的参数分支,依赖关系非常难以理解。
因此,该包的主要目的是预先准备一个实现接口和 trait,以使接口声明和预定义的接口和 trait 能够作为流包装器运行。
安装
{ "require": { "ryunosuke/stream-wrapper": "dev-master" } }
功能
目录结构
├── Exception/
├── Utils/
├── Stream/
├── Mixin
│ ├── DelegateTrait.php
│ ├── DirectoryIOTrait.php
│ ├── DirectoryIteratorTrait.php
│ ├── StreamTrait.php
│ ├── UrlIOTrait.php
│ └── UrlPermissionTrait.php
├── PhpStreamWrapperInterface.php
├── StreamWrapperAdapterInterface.php
├── StreamWrapperAdapterTrait.php
└── StreamWrapperNoopTrait.php
顶级目录
这就是这个包的目标。其他都是赠品。
PhpStreamWrapperInterface
这是从php原始流包装器中提取的 interface。
可能是因为php本身没有提供接口,我认为其中一部分原因可能是有些方法不应该被实现。例如,mkdir( https://php.ac.cn/manual/streamwrapper.mkdir.php )等,“如果包装器不支持目录创建,则不应定义此方法以返回适当的错误消息”。还有其他一些方法也有类似的说明。如果相信这些话,那么“为了返回适当的错误消息”只是一个程度的问题,因为接口不能为了错误消息而切断,这非常不方便。因此,在这个包中,我们决定以例外的方式处理接口。
实际上,这并不需要。由于本家没有提供接口,即使不使用也不会导致错误。为了将运行时错误转换为编译时错误,主要是在开发时准备的。
StreamWrapperAdapterInterface
这是本包中用于规范流包装器的 interface。
正如前面所述,标准流包装器的命名难以理解,或者有奇怪的情况划分,或者突然出现 context 这样的概念(并且是资源),所以这个 interface 切割了一个“函数名和接口名一致”的接口,并通过 StreamWrapperAdapterTrait 将其连接到本家。这样,context 或情况划分等都被吸收,形成了简洁的方法集和对应的函数映射结构。
StreamWrapperAdapterTrait
这是一个连接 StreamWrapperAdapterInterface 和 PhpStreamWrapperInterface 的 trait。
StreamWrapperAdapterInterface 仅仅是定义了一个接口规范,并不能作为流包装器运行。要使其运行,需要 PhpStreamWrapperInterface 的实现以及连接它们的样板代码。这就是这个 trait 承担的角色。
很少使用联合类型作为返回值(目前只有 readdir 是特例)。如果无法返回适当的值,则抛出 ErrorException,以便将其转换为 php 错误。
context 这个概念被取消了。context 被解析,并作为参数数组传递。context 中还有 options 和 params 这样的概念,所以它接受两个参数。然而,关于 params 的文档非常少,几乎没有人使用它(官方文档中只有一个 stream_notification_callback 的例子)。
StreamWrapperNoopTrait
实现了 StreamWrapperAdapterInterface 的所有方法,但都抛出异常的 trait。
在实现流包装器时,需要实现所有不使用的方法,因此这个 trait 出现了补充作用,它在所有方法中都抛出异常(有少数特例)。通过使用这个 trait,在实现类中只需要实现所需的方法,该类就可以作为流包装器运行。
class HogeStream implements PhpStreamWrapperInterface, // これにより、php オリジナルのストリームラッパーインターフェースがすべて規約されます(なくても動きますが、エラーが実行時になります) StreamWrapperAdapterInterface // これにより、オレオレストリームラッパーインターフェースがすべて規約されます { use StreamWrapperAdapterTrait; // オレオレストリームラッパーと php オリジナルのストリームラッパーを接続させるための trait です use StreamWrapperNoopTrait; // すべてが例外を投げるデフォルト実装 trait です public function _stat(string $url): array // これを実装すれば stat(filesize や filemtime) を実装したことになります { // ... } }
以下是方法的详细信息。其中一些进行了修改。阅读完毕后,您可能会对流包装器的混乱状态有更深的理解。
_mkdir
- 函数:
mkdir(string $directory, int $permissions = 0777, bool $recursive = false, ?resource $context = null): bool
- StreamWrapper:
mkdir(string $path, int $mode, int $options): bool
- ThisPackage:
_mkdir(string $url, int $permissions, bool $recursive, array $contextOptions, array $contextParams): bool
函数版本参数分开,但流版本以位标志的形式传递,因此我们将其分开调用。也就是说,接口版本 mkdir 和函数版本相同(context 除外)。
_rmdir
- 函数:
rmdir(string $directory, ?resource $context = null): bool
- StreamWrapper:
rmdir(string $path, int $options): bool
- ThisPackage:
_rmdir(string $url, array $contextOptions, array $contextParams): bool
实现时应实现的流版本有一个神秘的参数 $options,但我们没有实现它。文档中存在,“STREAM_MKDIR_RECURSIVE 等值的位掩码”,但没有这样的参数在函数版本中。确实,如果 rmdir 支持递归删除,那么这将是很有用的...我认为这是 mkdir 的复制粘贴错误。
_touch
- 函数:
touch(string $filename, ?int $mtime = null, ?int $atime = null): bool
- StreamWrapper:
stream_metadata(string $path, int $option, mixed $value): bool
- ThisPackage:
_touch(string $url, int $mtime, int $atime): bool
没有存在与函数版 tocuh 对应的流版本方法。在 stream_metadata 的参数分支中实现了。详细说明了“如果是 tocuh 的话...如果是 chmod 的话...”,接口和 trait 已经全部吸收了。也就是说,接口版本 tocuh 几乎与函数版本相同。“几乎”是因为 ?int 变成了 int。省略时或同时指定时的值都解决了,并传递过去了。
顺便提一下,命名规则有问题,参数是路径,所以我认为 stream_metadata 应该用 url_metadata 才正确。(大概是“因为是流包装器的函数”这个理由吧。但这样的话,utl_stat
的说明就不够了)。
_chmod
- 函数:
chmod(string $filename, int $permissions): bool
- StreamWrapper:
stream_metadata(string $path, int $option, mixed $value): bool
- ThisPackage:
_chmod(string $url, int $permissions): bool
没有与 chmod 函数对应的流函数。在 stream_metadata 的参数分叉中实现了。也就是说,在所有情况下都与 touch 的说明相同。
_chown
- 函数:
chown(string $filename, string|int $user): bool
- StreamWrapper:
stream_metadata(string $path, int $option, mixed $value): bool
- ThisPackage:
_chown(string $url, int $uid): bool
没有与 chown 函数对应的流函数。在 stream_metadata 的参数分叉中实现了。也就是说,在所有情况下都与 touch 的说明相同,但在调用时会使用变量名,所以 $uid 是 int。
_chgrp
- 函数:
chgrp(string $filename, string|int $group): bool
- StreamWrapper:
stream_metadata(string $path, int $option, mixed $value): bool
- ThisPackage:
_chgrp(string $url, int $gid): bool
没有与 chgrp 函数对应的流函数。在 stream_metadata 的参数分叉中实现了。也就是说,在所有情况下都与 touch 的说明相同,但在调用时会使用变量名,所以 $gid 是 int。
_unlink
- 函数:
unlink(string $filename, ?resource $context = null): bool
- StreamWrapper:
unlink(string $path): bool
- ThisPackage:
_unlink(string $url, array $contextOptions, array $contextParams): bool
没有什么变化。
_rename
- 函数:
rename(string $from, string $to, ?resource $context = null): bool
- StreamWrapper:
rename(string $path_from, string $path_to): bool
- ThisPackage:
_rename(string $src_url, string $dst_url, array $contextOptions, array $contextParams): bool
没有什么变化。
_stat
- 函数:
stat(string $filename): array|false
- StreamWrapper:
url_stat(string $path, int $flags): array|false
- ThisPackage:
_stat(string $url): array
没有什么变化。另外,由于是 stat,所以没有办法,这个方法对应了很多函数。在大多数自定义包装器中,实现是必需的(至少返回 size 也很好)。
_lstat
- 函数:
lstat(string $filename): array|false
- StreamWrapper:
url_stat(string $path, int $flags): array|false
- ThisPackage:
_lstat(string $url): array
没有对应的流方法。由于链接这个概念受文件系统的影响很大,所以作为流包装器调用的可能性几乎不存在。因此,在本包中,为了与函数一对一对应,特意分开了。因此,这个方法被特别处理,调用时会委派给默认的 _stat
而不是“未实现异常”。
_opendir
- 函数:
opendir(string $directory, ?resource $context = null): resource|false
- StreamWrapper:
dir_opendir(string $path, int $options): bool
- ThisPackage:
_opendir(string $url, array $contextOptions, array $contextParams): object
有应该实现的流版本中有一个神秘的参数 $options,但还没有实现。因为文档不存在,所以也不清楚这个参数是为了什么。最初以为它会对应 scandir 的 $sorting_order 参数,但似乎并不是这样。
由于这个 opendir 和 fopen 是打开流的函数,所以需要内部状态。本包不喜欢持有状态,所以用参数传递。也就是说,如果 _opendir 返回一个对象,那么这个对象将作为 _readdir, _closedir 的参数传递。通过这种规格,不需要保持打开的任何东西,例如属性。使用时也是因为“用 fopen 打开的资源传递给 fread 等使用”的用法,所以我认为这种规格更直观。在直接实现 StreamWrapper 时,“fread 没有参数,但是如何获取 fopen 的资源?”这样的情况很常见。
_readdir
- 函数:
readdir(?resource $dir_handle = null): string|false
- StreamWrapper:
dir_readdir(): string
- ThisPackage:
_readdir(object $resource): ?string
$dir_handle 的省略是不必要的,因此还没有实现。PHP 常见的是“参数是可选的,省略时使用最后一个资源”,这是过去的规定,现在几乎不再需要。
另外,原始值是 string|false
(*文档是 string,但可能是错误*),如果完成,则返回 null。这仅是因为它可以表示为 ?string
的原因。如果 PHP8 移行后可以使用联合类型,可能会将其改为 array|false
(个人原因),但这将是很久以后的事情。
_rewinddir
- 函数:
rewinddir(?resource $dir_handle = null): void
- StreamWrapper:
dir_rewinddir(): bool
- ThisPackage:
_rewinddir(object $resource): bool
没有什么变化。
_closedir
- 函数:
closedir(?resource $dir_handle = null): void
- StreamWrapper:
dir_closedir(): bool
- ThisPackage:
_closedir(object $resource): bool
没有什么变化。
_fopen
- 函数:
fopen(string $filename, string $mode, bool $use_include_path = false, ?resource $context = null): resource|false
- StreamWrapper:
stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
- ThisPackage:
_fopen(string $url, string $mode, array $contextOptions, array $contextParams): object
存在一些神秘的参数,但它们都被吸收了,因此从接口上看,它与 fopen
几乎相同。关于返回值,可以说与 _opendir
相同,这个 _fopen
返回的对象将被用作 _fread
、_fwrite
等方法的参数。
以下是修改部分。
首先,$options,正如其名所示,对应于 STREAM_USE_PATH
。它对应于 foepn
或 file_get_contents
的第三个参数。…但在这个包中几乎没有对应,也没有传递参数。这是因为,由于自定义流包装器的特性,$path 是所有从方案开始的完整路径,不会传入相对路径。此外,包含路径是基于文件方案考虑的,而自定义流的相对路径被设置为包含路径的情况几乎不存在。(在非常有限的情况下,fopen 无方案调用是存在的,因此 STREAM_USE_PATH 可能是为此功能而设计的,但在这里我们省略了它)。
接下来是 STREAM_REPORT_ERRORS
,但几乎找不到它被传递的情况。尽管如此,我们还是提供了一定的支持,并且当此标志未设置时,警告将被抑制(因此 fopen 的错误会报告,但无法获得详细的错误信息)。在检查 php-src 时,发现内部使用了 REPORT_ERRORS
,似乎它在某些情况下被抑制,例如在读取文件时不是文件打开的主要目的时。例如 finfo 或 exif 等。这非常不方便,因此可能会进行修正以始终输出错误。
$opened_path
几乎没有实现。这是从包含路径中找到的完整路径的接收参数,但正如上面所述,这种情况几乎不存在。
_fread
- 函数:
fread(resource $stream, int $length): string|false
- StreamWrapper:
stream_read(int $count): string|false
- ThisPackage:
_fread(object $resource, int $length): string
没有特别的变化。如果敢说的话,文档中有许多注意事项需要注意。
- 如果返回值比 count 长的话,将发生 E_WARNING 错误,并且将丢失额外的数据。
- streamWrapper::stream_eof() 在 streamWrapper::stream_read() 被调用后直接调用,以检查是否已达到 EOF。如果没有实现,则视为 EOF。
- 当使用 (file_get_contents() 等) 读取整个文件时,PHP 会先调用 streamWrapper::stream_read(),然后调用 streamWrapper::stream_eof()。但是,只要 streamWrapper::stream_read() 返回非空字符串,就会忽略 streamWrapper::stream_eof() 的返回值。
这是 PHP 的规范,因此不是包可以解决的问题,所以我们只引用它。
_fwrite
- 函数:
fwrite(resource $stream, string $data, ?int $length = null): int|false
- StreamWrapper:
stream_write(string $data): int
- ThisPackage:
_fwrite(object $resource, string $data): int
没有什么变化。
_ftruncate
- 函数:
ftruncate(resource $stream, int $size): bool
- StreamWrapper:
stream_truncate(int $new_size): bool
- ThisPackage:
_ftruncate(object $resource, int $size): bool
没有什么变化。
_fclose
- 函数:
fclose(resource $stream): bool
- StreamWrapper:
stream_close(): void
- ThisPackage:
_fclose(object $resource): bool
没有特别的变化。如果敢说的话,将返回值改为 bool。这是对AWS 也抱怨的,也许将来会进行更改…所以抱着一线希望将其作为 bool。
_ftell
- 函数:
ftell(resource $stream): int|false
- StreamWrapper:
stream_tell(): int
- ThisPackage:
_ftell(object $resource): int
看起来并没有什么变化。如果敢说的话,文档中提到“这个方法是对应fseek()调用的,用于确定当前位置”,这并不是错误的。通过追踪PHP邮件列表(URL忘记),发现由于一些深远的原因,它是这样编写的,所以这是正确的。关于这一点,我将简要涉及seek的相关内容。
_fseek
- 函数:
fseek(resource $stream, int $offset, int $whence = SEEK_SET): int
- StreamWrapper:
stream_seek(int $offset, int $whence = SEEK_SET): bool
- ThisPackage:
_fseek(object $resource, int $offset, int $whence): bool
看起来并没有什么变化。如果敢说的话,文档中的“当前实现不会将whence的值设置为SEEK_CUR。这样的seek会被内部转换为SEEK_SET”是错误的。确认至少在Windows上,使用文件标志"a"打开fopen时可以通过SEEK_CUR调用确认。也就是说,需要实现SEEK_CUR。
顺便说一句,关于_ftell
中提到的tell和seek,以下内容可能有参考价值。
- 在成功的情况下,在调用streamWrapper::stream_seek()后立即调用streamWrapper::stream_tell()。如果streamWrapper::stream_tell()失败,则调用原函数的返回值设置为false。
_feof
- 函数:
feof(resource $stream): bool
- StreamWrapper:
stream_eof(): bool
- ThisPackage:
_feof(object $resource): bool
没有什么变化。
_fflush
- 函数:
fflush(resource $stream): bool
- StreamWrapper:
stream_flush(): bool
- ThisPackage:
_fflush(object $resource): bool
没有什么变化。
_flock
- 函数:
flock(resource $stream, int $operation, int &$would_block = null): bool
- StreamWrapper:
stream_lock(int $operation): bool
- ThisPackage:
_flock(object $resource, int $operation): bool
“锁被阻塞”情况下的接收参数$would_block不对应。因为没有在流端对应该参数,所以无法向调用方传递值。
_fstat
- 函数:
fstat(resource $stream): array|false
- StreamWrapper:
stream_stat(): array|false
- ThisPackage:
_fstat(object $resource): array
没有什么变化。
_stream_set_blocking
- 函数:
stream_set_blocking(resource $stream, bool $enable): bool
- StreamWrapper:
stream_set_option(int $option, int $arg1, int $arg2): bool
- ThisPackage:
_stream_set_blocking(object $resource, bool $enable): bool
这也像touch一样,由于参数分支,所以将其分到单独的方法中,以便它们被调用。除此之外,并没有什么变化。
_stream_set_read_buffer
- 函数:
stream_set_read_buffer(resource $stream, int $size): int
- StreamWrapper:
stream_set_option(int $option, int $arg1, int $arg2): bool
- ThisPackage:
_stream_set_read_buffer(object $resource, int $size): bool
这也像touch一样,由于参数分支,所以将其分到单独的方法中,以便它们被调用。除此之外,并没有什么变化。如果敢说的话,文档对ReadBuffer没有任何提及,但它确实被调用了(可能是文档错误?)。另外,返回值是特殊的,“成功时返回0,无法按要求设置时返回其他值”。大多数情况下,可以通过错误消息来判断,所以这里使用了bool类型。
_stream_set_write_buffer
- 函数:
stream_set_write_buffer(resource $stream, int $size): int
- StreamWrapper:
stream_set_option(int $option, int $arg1, int $arg2): bool
- ThisPackage:
_stream_set_write_buffer(object $resource, int $size): bool
这也像touch一样,由于参数分支,所以将其分到单独的方法中,以便它们被调用。除此之外,并没有什么变化。与_stream_set_read_buffer相同。
_stream_set_timeout
- 函数:
stream_set_timeout(resource $stream, int $seconds, int $microseconds = 0): bool
- StreamWrapper:
stream_set_option(int $option, int $arg1, int $arg2): bool
- ThisPackage:
_stream_set_timeout(object $resource, float $timeout): bool
这也像touch一样,由于参数分支,所以将其分到单独的方法中,以便它们被调用。在函数版中,超时指定是$seconds+$microseconds,但为了易于理解,将其合并为float。例如,要将2.5秒设置为超时,应指定为(2.5)
而不是(2, 500000)
。这可能是原始syscall是裸露的,所以不需要这么高的精度。
此外,实现此方法的需求几乎不存在。虽然可以设置超时并实现超时,但由于没有提供传递给调用方的技巧(stream_get_meta_data的返回值),所以不能实现。如果stream_read有像&$timedout这样的接收参数,则可以实现...
_stream_select
- 函数:
stream_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, ?int $microseconds = null): int|false
- StreamWrapper:
stream_cast(int $cast_as): resource
- ThisPackage:
_stream_select(object $resource, int $cast_as): resource
函数版本的 stream_select
函数经常使用,但关于如何映射到流版本的 stream_cast
的信息不足,因此,此函数特别处理,不抛出“未实现异常”,而是使用默认实现 return false
。这是因为,如果不使用,则不需要实现。但是,似乎在调用 mime_content_type
时会调用 stream_select(cast)
。实际上,返回 false 也能正常工作,所以就这样做了。
对应表
以下是对应表。由于篇幅原因,省略了类型和默认值。
Mixin
实际上,StreamWrapper 并不是简单地实现一个单元就足够了,多个方法相互关联。例如,调用 file_get_contents
会调用 fopen
、fread
、feof
等各种方法。意想不到的是,调用 include/require
也会调用 stream_set_read_buffer
。还有,上述的 mime_content_type
中的 stream_cast
也非常意外。不可能一一实现这些,而且大多数Wrapper的实现几乎相同,因此预先定义了一组trait。
StreamTrait
用于读取和写入流的trait。内部进行缓冲。不太可能存在不进行缓冲的StreamWrapper,大多数Wrapper在执行流操作时都需要缓冲。在这种情况下,只要使用它,就可以完成所有流操作。由于是trait,如果无法处理,则可以单独定义方法。
此外,通常所说的“缓冲区”与这里的略有不同。简单地说,“打开时一次性读取全部,flush时一次性写入全部”的缓冲区。在实际的IO中,部分写入的“真正的”缓冲区实现难度大,而有用性低,在大多数用例中都不需要,因此这样实现。
DirectoryIOTrait
支持目录的scheme的trait。虽然只有mkdir/rmdir两种函数(如果要说的话,rename/stat也),但mkdir有递归处理,rmdir在有内容的情况下不能删除,这些是共有处理,因此用trait提取出来。特别值得注意的是,实现很直接。
DirectoryIteratorTrait
为scandir提供的trait。为了使用scandir,需要实现opendir、readdir、rewinddir、closedir这四种方法,但在现代,IteratorAggregate、Generator、iterable等已经准备好了,因此不需要实现这四种方法。只要提供一个Iterator,所有的实现都应该完成。这是为此提供的trait。如果只是scandir,那么没问题,但如果直接调用rewinddir,则需要传递一个rewindable的Iterator。
另外,DirectoryIOTrait没有关联。即使没有目录的概念,也可以实现“以"/"为标记的目录探索”(S3是很好的例子)。
UrlIOTrait
为基于路径的函数提供输入输出的trait(filesize、rename、unlink等)。没有特别要注意的。实现很直接。
UrlPermissionTrait
为基于路径的函数提供权限(chmod、chown、chgrp)的trait。不执行权限控制。任何人都可以随时更改。理由是下面的实现太麻烦了。
- chmod: 大多数系统中,只有文件的所有者才能更改其模式
- chown: 只有超级用户才能更改文件的所有者
- chgrp: 只有超级用户才能任意更改文件所属的组。其他用户可以将其所属的组更改为其所属的组
隐含的公共API
所有的trait都有一个隐含的API。在代码上用abstract表示。例如,写入处理在UrlIOTrait和StreamTrait中都会使用,存在检查几乎在所有的trait中都会使用。因此,为了避免重复实现,有意将命名统一,以便一些trait中实现的函数可以在其他函数中重用。目前有以下方法。
function parent(): ?string;
function children(...): iterable;
function move(...): void;
URL操作系方法。文件和目录都可能被调用。
parent和children分别返回父URL(string)
和子URL(iterable)
。
function getMetadata(...): ?array;
function setMetadata(...): void;
元数据操作系方法。文件和目录都可能被调用。
getMetadata也兼有存在检查。如果条目不存在,则必须返回null。
function createDirectory(...): void;
function deleteDirectory(...): void;
目录操作系方法。如前所述,即使支持目录,也至少需要实现mkdir和rmdir。mkdir有递归选项,rmdir需要空检查。为此需要这些方法。不需要标准函数,或者始终需要递归操作,则不需要这些方法(trait)。在某些情况下,这可能更方便。
function selectFile(...): string;
function createFile(...): void;
function appendFile(...): void;
function deleteFile(...): void;
文件操作系方法。
selectFile有一个包含$metadata的引用参数。需要包含与getMetadata相同的数组。故意使用引用参数是因为在单次操作中可以同时获取元数据和内容,这样就不会浪费(selectFile+getMetadata可以原子操作时尤其如此)。
createFile有一个包含$metadata的参数。需要包含与setMetadata相同的数组。故意使用参数是因为在单次操作中可以同时更新元数据和内容,这样就不会浪费(createFile+setMetadata可以原子操作时尤其如此)。
appendFile是“a”模式下的追加处理。可以一次性读取全部再一次性保存,但有些情况下,可能需要专门的追加处理(例如mysql的CONCAT、redis的APPEND等),这样使用会大大提高效率。更不用说如果追加失败,那么就不应该使用“a”模式。
Stream
Stream中的○○Stream仿佛是这个包的主角,但实际上,所有都是参考实现。作者为了实现多样性而随意实现,并“感觉可以用了”,所以只是配置了一些东西。当然,也可以直接使用,但有很多是硬编码实现,完全没有复杂性。反复强调,本包的主角是顶级目录的interface+trait。
不过,有一点点思想,还是介绍一下。
scheme://hostname:port/path/to/?query#file.json
─── ────── ──── ── ────
│ │ │ │ └─ プロトコルにおける「キー名」です
│ │ │ └───── プロトコルの「パラメータ」です
│ │ └──────── プロトコルにおける「内部的な位置」を示します
│ └─────────────── プロトコルにおける「ネットワーク上の位置」を示します
└───────────────────── プロトコル名です
比如mysql,那么
- 网络上的位置:相当于DSN
- 内部位置:相当于模式·表名·主键
- 参数:相当于charset等
- 键名:(如果支持的话)相当于主键
例如 mysql://127.0.0.1:3306/dbname/tablename?charset=utf8mb4#pkval
这样写就很清晰。其他,如果redis,则是 redis://127.0.0.1:6379/dbindex/key
,如果是S3,则是 s3://endpoint/bucket/objectname
。内置的zip scheme使用fragment表示内部文件,因此是 zip:///path/to/file.zip#localname.txt
的形式。
这样,“通过URL设置KVS规范,使所有协议的StreamWrapper都可以像KVS一样使用”是目标作为 存在 的。但实际上,正如上面所述,是为了实现多样性而提供的参考实现。
首先,如果是使用MySQL,PDO或Doctrine都是有的,所以使用流包装器的必要性几乎为零。S3有AWS官方的流包装器实现。以下是其实施的目的:
- 全部:仅使用interface+trait无法编写测试,因为“这样做是否真的正确”并不明确,因此有必要实现实际的流(实际上,在实现的过程中产生了多样性)
- Array:为了高速测试和与file方案的完全兼容
- Mysql:由于schema full和flock
- php:出于个人用途
- Redis:由于schemaless和TTL(context)
- S3:由于对伪目录的支持
- Smtp:如果有一个只写流的SMTP将很有趣(几乎是即兴想到的)
- Zip:由于特殊访问(片段为内部文件名)
许可证
MIT
发布
版本号遵循浪漫版本号规范(不是语义版本号)。
- 主要版本:在大规模兼容性破坏时升级(架构、类结构的更改等)
- 次要版本:在小规模兼容性破坏时升级(参数更改、类型提示的添加等)
- 补丁版本:没有兼容性破坏(默认参数的添加、新类的添加、代码格式等)
1.1.2
- [feature] 修复php8.2的错误
- [fixbug] 修复没有扩展名时也会出现点的问题
1.1.1
- [feature] 可以附加扩展名等附加信息的PHP协议
- [feature] 将到标准协议的委派trait
- [feature] 支持URL的本地协议
- [feature] URL的细分
1.1.0
- [*change] 简化flock
1.0.0
- 发布