Codex's updates...

This commit is contained in:
2026-10-18 19:27:17 -04:00
parent a1643eb1b7
commit 02223083f6
338 changed files with 14757 additions and 7986 deletions
+1
View File
@@ -71,4 +71,5 @@ return $config->setRules([
])
->setFinder($finder)
->setRiskyAllowed(true)
->setUnsupportedPhpVersionAllowed(true)
->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect());
+1 -1
View File
@@ -1 +1 @@
php 8.4.10
php 8.5.0
+1 -1
View File
@@ -32,7 +32,7 @@
"ext-zip": "*",
"mikey179/vfsstream": "^1.6",
"php-coveralls/php-coveralls": "^2.5",
"friendsofphp/php-cs-fixer": "^3.16",
"friendsofphp/php-cs-fixer": "^3.86",
"vimeo/psalm": "^6.0",
"brianium/paratest": "^7.7"
},
+245
View File
@@ -0,0 +1,245 @@
Compressing large files to S3 compatible storages
=============
S3 compatible storages usually have a limitation of 5 GiB when using single upload. When generating larger zips, the
solution is to use a `multi-part upload`_.
.. _multi-part upload: <https://github.com/awsdocs/aws-doc-sdk-examples/blob/75a8bf8536d436db91ec5de1ba0ed80fd258e904/php/example_code/s3/s3-multipart-upload-using-lowlevel-php-sdk-api.php>`
We can implement a `PSR-7 stream <https://www.php-fig.org/psr/psr-7/>`_ that buffers ZipStream's output and uploads
to S3 in chunks.
MultipartUploadBufferStream example
---------------
.. code-block:: php
<?php
declare(strict_types=1);
use Aws\S3\S3ClientInterface;
use Psr\Http\Message\StreamInterface;
use function strlen;
use function substr;
final class MultipartUploadBufferStream implements StreamInterface
{
private const int PART_SIZE = 5242880; // 5 MiB in bytes
private string $buffer = '';
private int $bufferSize = 0;
private int $partNumber = 1;
private array $parts = [];
private string $uploadId;
public function __construct(
private readonly string $destinationFileName,
private readonly string $bucket,
private readonly S3ClientInterface $client
) {
$result = $this->client->createMultipartUpload(
[
'Bucket' => $bucket,
'Key' => $destinationFileName,
'StorageClass' => 'REDUCED_REDUNDANCY',
]
);
$this->uploadId = $result['UploadId'];
$this->parts['Parts'] = [];
}
public function write($string): int
{
$chunkSize = strlen($string);
$this->buffer .= $string;
$this->bufferSize += $chunkSize;
if ($this->bufferSize >= self::PART_SIZE) {
$this->uploadPart();
}
return $chunkSize;
}
public function close(): void
{
// Upload remaining closing bytes from zip
$this->uploadPart();
$this->client->completeMultipartUpload([
'Bucket' => $this->bucket,
'Key' => $this->destinationFileName,
'UploadId' => $this->uploadId,
'MultipartUpload' => $this->parts,
]);
$this->buffer = '';
$this->bufferSize = 0;
}
private function uploadPart(): void
{
$result = $this->client->uploadPart([
'Bucket' => $this->bucket,
'Key' => $this->destinationFileName,
'UploadId' => $this->uploadId,
'PartNumber' => $this->partNumber,
'Body' => $this->buffer,
]);
$this->buffer = '';
$this->bufferSize = 0;
$this->parts['Parts'][$this->partNumber] = [
'PartNumber' => $this->partNumber,
'ETag' => $result['ETag'],
];
$this->partNumber++;
$result = null;
gc_collect_cycles(); // To avoid memory leaks. @see github.com/aws/aws-sdk-php/issues/1273
}
public function __toString(): string
{
return $this->getContents();
}
public function getContents(): string
{
$buffer = $this->buffer;
$this->buffer = '';
$this->bufferSize = 0;
return $buffer;
}
public function detach(): null
{
$this->close();
return null;
}
public function getSize(): int
{
return $this->bufferSize;
}
public function isReadable(): bool
{
return true;
}
public function isWritable(): bool
{
return true;
}
public function isSeekable(): bool
{
return false;
}
public function rewind(): void
{
$this->seek(0);
}
public function seek($offset, $whence = SEEK_SET): void
{
throw new \RuntimeException('Cannot seek a BufferStream');
}
public function eof(): bool
{
return $this->bufferSize === 0;
}
public function tell(): int
{
throw new \RuntimeException('Cannot determine the position of a BufferStream');
}
public function read($length): string
{
$currentLength = $this->bufferSize;
if ($length >= $currentLength) {
// No need to slice the buffer because we don't have enough data.
$result = $this->buffer;
$this->buffer = '';
$this->bufferSize = 0;
} else {
// Slice up the result to provide a subset of the buffer.
$result = substr($this->buffer, 0, $length);
$this->buffer = substr($this->buffer, $length);
$this->bufferSize -= $length;
}
return $result;
}
public function getMetadata($key = null)
{
return $key ? null : [];
}
}
Then, we can use it with ZipStream to compress the files and upload the parts to the storage.
ZipStream usage
---------------
.. code-block:: php
<?php
declare(strict_types=1);
use Aws\S3\S3Client;
use Aws\Credentials\CredentialProvider;
use MultipartUploadBufferStream;
use ZipStream\ZipStream;
$bucket = 'your bucket name';
$client = new S3Client([
'region' => 'your region',
'version' => 'latest',
'bucketName' => $bucket,
'credentials' => CredentialProvider::defaultProvider(),
]);
$bufferStream = new MultipartUploadBufferStream(
'destination-file.zip',
$bucket,
$client,
);
$zip = new ZipStream(
outputStream: $destination,
defaultCompressionMethod: CompressionMethod::STORE,
defaultEnableZeroHeader: true,
sendHttpHeaders: false,
);
$zip->addFile(
fileName: 'big-file-1.txt',
data: 'File1 data',
);
$zip->addFile(
fileName: 'big-file-2.txt',
data: 'File2 data',
);
$zip->finish();
$destination->close(); // Needed after $zip->finish() to upload the last remaining bytes to S3
You can read more about the logic behind this implementation in the `discussion`_.
.. _discussion: https://github.com/maennchen/ZipStream-PHP/discussions/402
+1
View File
@@ -16,6 +16,7 @@ user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
Nginx
Varnish
ContentLength
LargeFilesToS3
Installation
---------------
+9 -9
View File
@@ -110,8 +110,8 @@ final class File
$detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null);
if (
$this->isSimulation() &&
$detectedSize !== null
$this->isSimulation()
&& $detectedSize !== null
) {
$this->uncompressedSize = $detectedSize;
$this->compressedSize = $detectedSize;
@@ -202,10 +202,10 @@ final class File
compressionMethod: $this->compressionMethod,
lastModificationDateTime: $this->lastModificationDateTime,
crc32UncompressedData: $this->crc,
compressedSize: $zip64Enabled
compressedSize: ($forceEnableZip64 || $this->compressedSize > 0xFFFFFFFF)
? 0xFFFFFFFF
: $this->compressedSize,
uncompressedSize: $zip64Enabled
uncompressedSize: ($forceEnableZip64 || $this->uncompressedSize > 0xFFFFFFFF)
? 0xFFFFFFFF
: $this->uncompressedSize,
fileName: $this->fileName,
@@ -238,8 +238,8 @@ final class File
// Sets Bit 11: Language encoding flag (EFS). If this bit is set,
// the filename and comment fields for this file
// MUST be encoded using UTF-8. (see APPENDIX D)
if (mb_check_encoding($this->fileName, 'UTF-8') &&
mb_check_encoding($this->comment, 'UTF-8')) {
if (mb_check_encoding($this->fileName, 'UTF-8')
&& mb_check_encoding($this->comment, 'UTF-8')) {
$this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS;
}
}
@@ -321,9 +321,9 @@ final class File
$deflate = $this->compressionInit();
while (
!feof($this->unpackStream()) &&
($this->maxSize === null || $this->uncompressedSize < $this->maxSize) &&
($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
!feof($this->unpackStream())
&& ($this->maxSize === null || $this->uncompressedSize < $this->maxSize)
&& ($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
) {
$readLength = min(
($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize,
+6 -6
View File
@@ -29,11 +29,11 @@ abstract class Time
[$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s'));
return
((int) $year << 25) |
((int) $month << 21) |
((int) $day << 16) |
((int) $hour << 11) |
((int) $minute << 5) |
((int) $second >> 1);
((int) $year << 25)
| ((int) $month << 21)
| ((int) $day << 16)
| ((int) $hour << 11)
| ((int) $minute << 5)
| ((int) $second >> 1);
}
}
@@ -23,10 +23,10 @@ abstract class ExtendedInformationExtraField
new PackField(format: 'v', value: self::TAG),
new PackField(
format: 'v',
value: ($originalSize === null ? 0 : 8) +
($compressedSize === null ? 0 : 8) +
($relativeHeaderOffset === null ? 0 : 8) +
($diskStartNumber === null ? 0 : 4)
value: ($originalSize === null ? 0 : 8)
+ ($compressedSize === null ? 0 : 8)
+ ($relativeHeaderOffset === null ? 0 : 8)
+ ($diskStartNumber === null ? 0 : 4)
),
...($originalSize === null ? [] : [
new PackField(format: 'P', value: $originalSize),
+3 -3
View File
@@ -714,9 +714,9 @@ class ZipStream
}
// Add 64bit headers (if applicable)
if (count($this->centralDirectoryRecords) >= 0xFFFF ||
$centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF ||
$sizeOfCentralDirectory > 0xFFFFFFFF) {
if (count($this->centralDirectoryRecords) >= 0xFFFF
|| $centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF
|| $sizeOfCentralDirectory > 0xFFFFFFFF) {
if (!$this->enableZip64) {
throw new OverflowException();
}
+15 -6
View File
@@ -24,7 +24,9 @@ final class CallbackOutputTest extends TestCase
$buf = '';
$zip = new ZipStream(
outputStream: CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf): void { $buf .= $chunk; }
static function (string $chunk) use (&$buf): void {
$buf .= $chunk;
}
),
sendHttpHeaders: false
);
@@ -53,10 +55,14 @@ final class CallbackOutputTest extends TestCase
$buf2 = '';
$stream1 = CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf1): void { $buf1 .= $chunk; }
static function (string $chunk) use (&$buf1): void {
$buf1 .= $chunk;
}
);
$stream2 = CallbackStreamWrapper::open(
static function (string $chunk) use (&$buf2): void { $buf2 .= $chunk; }
static function (string $chunk) use (&$buf2): void {
$buf2 .= $chunk;
}
);
$this->assertIsResource($stream1);
@@ -112,7 +118,8 @@ final class CallbackOutputTest extends TestCase
public function testStreamPositionTracking(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
static function (string $chunk): void { // no-op
}
);
$this->assertSame(0, ftell($stream));
@@ -129,7 +136,8 @@ final class CallbackOutputTest extends TestCase
public function testInvalidModeRejection(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
static function (string $chunk): void { // no-op
}
);
// Close the stream first
@@ -143,7 +151,8 @@ final class CallbackOutputTest extends TestCase
public function testStreamStatistics(): void
{
$stream = CallbackStreamWrapper::open(
static function (string $chunk): void { /* no-op */ }
static function (string $chunk): void { // no-op
}
);
fwrite($stream, 'test data');
@@ -35,26 +35,26 @@ class CentralDirectoryFileHeaderTest extends TestCase
$this->assertSame(
bin2hex($header),
'504b0102' . // 4 bytes; central file header signature
'0306' . // 2 bytes; version made by
'2d00' . // 2 bytes; version needed to extract
'2222' . // 2 bytes; general purpose bit flag
'0800' . // 2 bytes; compression method
'2008' . // 2 bytes; last mod file time
'2154' . // 2 bytes; last mod file date
'11111111' . // 4 bytes; crc-32
'77777777' . // 4 bytes; compressed size
'99999999' . // 4 bytes; uncompressed size
'0800' . // 2 bytes; file name length (n)
'0c00' . // 2 bytes; extra field length (m)
'0c00' . // 2 bytes; file comment length (o)
'0000' . // 2 bytes; disk number start
'0000' . // 2 bytes; internal file attributes
'20000000' . // 4 bytes; external file attributes
'34120000' . // 4 bytes; relative offset of local header
'746573742e706e67' . // n bytes; file name
'736f6d6520636f6e74656e74' . // m bytes; extra field
'736f6d6520636f6d6d656e74' // o bytes; file comment
'504b0102' // 4 bytes; central file header signature
. '0306' // 2 bytes; version made by
. '2d00' // 2 bytes; version needed to extract
. '2222' // 2 bytes; general purpose bit flag
. '0800' // 2 bytes; compression method
. '2008' // 2 bytes; last mod file time
. '2154' // 2 bytes; last mod file date
. '11111111' // 4 bytes; crc-32
. '77777777' // 4 bytes; compressed size
. '99999999' // 4 bytes; uncompressed size
. '0800' // 2 bytes; file name length (n)
. '0c00' // 2 bytes; extra field length (m)
. '0c00' // 2 bytes; file comment length (o)
. '0000' // 2 bytes; disk number start
. '0000' // 2 bytes; internal file attributes
. '20000000' // 4 bytes; external file attributes
. '34120000' // 4 bytes; relative offset of local header
. '746573742e706e67' // n bytes; file name
. '736f6d6520636f6e74656e74' // m bytes; extra field
. '736f6d6520636f6d6d656e74' // o bytes; file comment
);
}
}
+4 -4
View File
@@ -17,10 +17,10 @@ class DataDescriptorTest extends TestCase
compressedSize: 0x77777777,
uncompressedSize: 0x99999999,
)),
'504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
'11111111' . // 4 bytes; CRC-32 of uncompressed data
'77777777' . // 4 bytes; Compressed size
'99999999' // 4 bytes; Uncompressed size
'504b0708' // 4 bytes; Optional data descriptor signature = 0x08074b50
. '11111111' // 4 bytes; CRC-32 of uncompressed data
. '77777777' // 4 bytes; Compressed size
. '99999999' // 4 bytes; Uncompressed size
);
}
}
@@ -21,15 +21,15 @@ class EndOfCentralDirectoryTest extends TestCase
centralDirectoryStartOffsetOnDisk: 0x33,
zipFileComment: 'foo',
)),
'504b0506' . // 4 bytes; end of central dir signature 0x06054b50
'0000' . // 2 bytes; number of this disk
'0000' . // 2 bytes; number of the disk with the start of the central directory
'1000' . // 2 bytes; total number of entries in the central directory on this disk
'1000' . // 2 bytes; total number of entries in the central directory
'22000000' . // 4 bytes; size of the central directory
'33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number
'0300' . // 2 bytes; .ZIP file comment length
bin2hex('foo')
'504b0506' // 4 bytes; end of central dir signature 0x06054b50
. '0000' // 2 bytes; number of this disk
. '0000' // 2 bytes; number of the disk with the start of the central directory
. '1000' // 2 bytes; total number of entries in the central directory on this disk
. '1000' // 2 bytes; total number of entries in the central directory
. '22000000' // 4 bytes; size of the central directory
. '33000000' // 4 bytes; offset of start of central directory with respect to the starting disk number
. '0300' // 2 bytes; .ZIP file comment length
. bin2hex('foo')
);
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ class EndlessCycleStream implements StreamInterface
throw new RuntimeException('Infinite Stream!');
}
public function getMetadata(?string $key = null): array|null
public function getMetadata(?string $key = null): ?array
{
return $key !== null ? null : [];
}
+13 -13
View File
@@ -29,19 +29,19 @@ class LocalFileHeaderTest extends TestCase
$this->assertSame(
bin2hex((string) $header),
'504b0304' . // 4 bytes; Local file header signature
'2d00' . // 2 bytes; Version needed to extract (minimum)
'2222' . // 2 bytes; General purpose bit flag
'0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
'2008' . // 2 bytes; File last modification time
'2154' . // 2 bytes; File last modification date
'11111111' . // 4 bytes; CRC-32 of uncompressed data
'77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
'99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
'0800' . // 2 bytes; File name length (n)
'0c00' . // 2 bytes; Extra field length (m)
'746573742e706e67' . // n bytes; File name
'736f6d6520636f6e74656e74' // m bytes; Extra field
'504b0304' // 4 bytes; Local file header signature
. '2d00' // 2 bytes; Version needed to extract (minimum)
. '2222' // 2 bytes; General purpose bit flag
. '0800' // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
. '2008' // 2 bytes; File last modification time
. '2154' // 2 bytes; File last modification date
. '11111111' // 4 bytes; CRC-32 of uncompressed data
. '77777777' // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
. '99999999' // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
. '0800' // 2 bytes; File name length (n)
. '0c00' // 2 bytes; Extra field length (m)
. '746573742e706e67' // n bytes; File name
. '736f6d6520636f6e74656e74' // m bytes; Extra field
);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@ namespace ZipStream\Test;
trait Tempfile
{
protected string|null $tempfile;
protected ?string $tempfile;
/**
* @var resource
@@ -19,10 +19,10 @@ class DataDescriptorTest extends TestCase
$this->assertSame(
bin2hex($descriptor),
'504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
'11111111' . // 4 bytes; CRC-32 of uncompressed data
'6666666677777777' . // 8 bytes; Compressed size
'8888888899999999' // 8 bytes; Uncompressed size
'504b0708' // 4 bytes; Optional data descriptor signature = 0x08074b50
. '11111111' // 4 bytes; CRC-32 of uncompressed data
. '6666666677777777' // 8 bytes; Compressed size
. '8888888899999999' // 8 bytes; Uncompressed size
);
}
}
@@ -19,10 +19,10 @@ class EndOfCentralDirectoryLocatorTest extends TestCase
$this->assertSame(
bin2hex($descriptor),
'504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
'11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory
'3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record
'44444444' // 4 bytes;total number of disks
'504b0607' // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
. '11111111' // 4 bytes; number of the disk with the start of the zip64 end of central directory
. '3333333322222222' // 28 bytes; relative offset of the zip64 end of central directory record
. '44444444' // 4 bytes;total number of disks
);
}
}
@@ -25,17 +25,17 @@ class EndOfCentralDirectoryTest extends TestCase
$this->assertSame(
bin2hex($descriptor),
'504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50
'2f00000000000000' . // 8 bytes; size of zip64 end of central directory record
'3333' . // 2 bytes; version made by
'4444' . // 2 bytes; version needed to extract
'55555555' . // 4 bytes; number of this disk
'66666666' . // 4 bytes; number of the disk with the start of the central directory
'8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk
'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory
'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory
'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number
bin2hex('foo')
'504b0606' // 4 bytes;zip64 end of central dir signature - 0x06064b50
. '2f00000000000000' // 8 bytes; size of zip64 end of central directory record
. '3333' // 2 bytes; version made by
. '4444' // 2 bytes; version needed to extract
. '55555555' // 4 bytes; number of this disk
. '66666666' // 4 bytes; number of the disk with the start of the central directory
. '8888888877777777' // 8 bytes; total number of entries in the central directory on this disk
. 'aaaaaaaa99999999' // 8 bytes; total number of entries in the central directory
. 'ccccccccbbbbbbbb' // 8 bytes; size of the central directory
. 'eeeeeeeedddddddd' // 8 bytes; offset of start of central directory with respect to the starting disk number
. bin2hex('foo')
);
}
}
@@ -20,12 +20,12 @@ class ExtendedInformationExtraFieldTest extends TestCase
$this->assertSame(
bin2hex($extraField),
'0100' . // 2 bytes; Tag for this "extra" block type
'1c00' . // 2 bytes; Size of this "extra" block
'6666666677777777' . // 8 bytes; Original uncompressed file size
'8888888899999999' . // 8 bytes; Size of compressed data
'1111111122222222' . // 8 bytes; Offset of local header record
'33333333' // 4 bytes; Number of the disk on which this file starts
'0100' // 2 bytes; Tag for this "extra" block type
. '1c00' // 2 bytes; Size of this "extra" block
. '6666666677777777' // 8 bytes; Original uncompressed file size
. '8888888899999999' // 8 bytes; Size of compressed data
. '1111111122222222' // 8 bytes; Offset of local header record
. '33333333' // 4 bytes; Number of the disk on which this file starts
);
}
@@ -35,8 +35,8 @@ class ExtendedInformationExtraFieldTest extends TestCase
$this->assertSame(
bin2hex($extraField),
'0100' . // 2 bytes; Tag for this "extra" block type
'0000' // 2 bytes; Size of this "extra" block
'0100' // 2 bytes; Tag for this "extra" block type
. '0000' // 2 bytes; Size of this "extra" block
);
}
}
+24 -9
View File
@@ -62,10 +62,10 @@ class ZipStreamTest extends TestCase
$name = 'árvíztűrő tükörfúrógép.txt';
$content = 'Sample String Data';
$comment =
'Filename has every special characters ' .
'from Hungarian language in lowercase. ' .
'In uppercase: ÁÍŰŐÜÖÚÓÉ';
$comment
= 'Filename has every special characters '
. 'from Hungarian language in lowercase. '
. 'In uppercase: ÁÍŰŐÜÖÚÓÉ';
$zip->addFile(fileName: $name, data: $content, comment: $comment);
$zip->finish();
@@ -327,11 +327,14 @@ class ZipStreamTest extends TestCase
defaultEnableZeroHeader: false,
);
if (file_exists('/dev/null')) {
$streamUnseekable = fopen('/dev/null', 'w+');
} elseif (file_exists('NUL')) {
$streamUnseekable = fopen('NUL', 'w+');
} else {
foreach (['cat', 'more'] as $cmd) {
$streamUnseekable = @popen($cmd, 'w');
if ($streamUnseekable !== false) {
break;
}
}
if ($streamUnseekable === false) {
$this->markTestSkipped('Needs file /dev/null');
}
@@ -546,12 +549,24 @@ class ZipStreamTest extends TestCase
);
}
$zip->addDirectory('mydir/');
$zip->finish();
$tmpDir = $this->validateAndExtractZip($this->tempfile);
$files = $this->getRecursiveFileList($tmpDir);
$this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files);
$this->assertFileDoesNotContain(
$this->tempfile,
PackField::pack(
new PackField(format: 'V', value: 0x00000000), // CRC-32 = 0 (directory)
new PackField(format: 'V', value: 0xFFFFFFFF), // compressedSize — bug sentinel
new PackField(format: 'V', value: 0xFFFFFFFF), // uncompressedSize — bug sentinel
new PackField(format: 'v', value: 6), // filename length = strlen('mydir/')
)
);
}
#[Group('slow')]
@@ -15,8 +15,8 @@ class ExtendedInformationExtraFieldTest extends TestCase
$this->assertSame(
bin2hex((string) $extraField),
'5356' . // 2 bytes; Tag for this "extra" block type
'0000' // 2 bytes; TODO: Document
'5356' // 2 bytes; Tag for this "extra" block type
. '0000' // 2 bytes; TODO: Document
);
}
}