Skip to content

Commit f9fcb58

Browse files
adamzielreimic
andauthored
Add ZipStreamWriter to stream-write zip archives on PHP 7.2+ (#103)
This pull request introduces enhancements to the `ZipStreamWriter` class, enabling efficient file streaming and compression when writing ZIP archives. Key features include: 1. **Streaming File into ZIP**: Implements a method to stream a file from disk directly into a ZIP archive without loading the entire file into memory. 2. **Handling Central Directory**: Implements a method to write the central directory entries and the end-of-central-directory record, finalizing the ZIP archive. 3. **Deflate Compression**: Supports optional deflate compression for files being added to the ZIP archive. Closes #88 ### Major Changes 1. **`writeFileFromPath` Method**: - Reads the source file from disk in two passes: - First pass: Computes CRC32, uncompressed size, and compressed size without buffering the entire file. - Second pass: Streams the file's compressed data directly into the ZIP archive. - Supports deflate compression using `deflate_add`. 2. **`flush_directory_index` Method**: - Collects and writes central directory entries to the ZIP stream. - Writes the end-of-central-directory record to finalize the ZIP structure. ### Example Usage ```php use WordPress\Zip\ZipStreamWriter; // File paths $sourcePathOnDisk = '/path/to/source/file.txt'; $targetPathInZip = 'archive/file.txt'; // Create a file pointer for the output ZIP file $zipFilePointer = fopen('output.zip', 'wb'); // Instantiate the ZipStreamWriter $zipWriter = new ZipStreamWriter($zipFilePointer); // Write a file from the filesystem into the ZIP archive $zipWriter->writeFileFromPath($sourcePathOnDisk, $targetPathInZip, true); // Use 'false' for no compression // Finalize the ZIP file $zipWriter->flush_directory_index(); fclose($zipFilePointer); ``` ### How to Test 1. Clone the repository and checkout the branch with these changes. 2. Ensure PHPUnit is installed. 3. Run the test suite using the command: ```sh vendor/bin/phpunit ``` 4. Verify that all tests pass, indicating the functionality works as expected. ### Notes - This implementation aims to handle large files efficiently by streaming data in chunks. - The `flush_directory_index` method should be called once all file entries have been written to ensure the ZIP archive is finalized correctly. Feel free to provide any feedback or request further modifications as needed. --------- Co-authored-by: Michael Reichardt <30837295+reimic@users.noreply.github.com>
1 parent 87afea1 commit f9fcb58

File tree

6 files changed

+565
-5
lines changed

6 files changed

+565
-5
lines changed

.github/workflows/phpunit-tests-run.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
with:
3737
php-version: '${{ inputs.php }}'
3838
tools: phpunit-polyfills
39+
extensions: zip
3940

4041
- name: Install Composer dependencies
4142
uses: ramsey/composer-install@v3

.github/workflows/phpunit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
fail-fast: false
1818
matrix:
1919
os: [ ubuntu-latest, macos-latest, windows-latest ]
20-
php: [ '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ]
20+
php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ]
2121

2222
with:
2323
os: ${{ matrix.os }}

src/WordPress/Zip/ZipCentralDirectoryEntry.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
class ZipCentralDirectoryEntry {
66

7+
const HEADER_SIZE = 46;
8+
79
public $isDirectory;
810
public $firstByteAt;
911
public $versionCreated;
@@ -18,7 +20,6 @@ class ZipCentralDirectoryEntry {
1820
public $diskNumber;
1921
public $internalAttributes;
2022
public $externalAttributes;
21-
public $lastByteAt;
2223
public $path;
2324
public $extra;
2425
public $fileComment;
@@ -37,15 +38,13 @@ public function __construct(
3738
int $internalAttributes,
3839
int $externalAttributes,
3940
int $firstByteAt,
40-
int $lastByteAt,
4141
string $path,
4242
string $extra,
4343
string $fileComment
4444
) {
4545
$this->fileComment = $fileComment;
4646
$this->extra = $extra;
4747
$this->path = $path;
48-
$this->lastByteAt = $lastByteAt;
4948
$this->externalAttributes = $externalAttributes;
5049
$this->internalAttributes = $internalAttributes;
5150
$this->diskNumber = $diskNumber;
@@ -65,4 +64,13 @@ public function __construct(
6564
public function isFileEntry() {
6665
return false;
6766
}
67+
68+
public function size() {
69+
return (
70+
self::HEADER_SIZE +
71+
strlen($this->path) +
72+
strlen($this->extra) +
73+
strlen($this->fileComment)
74+
);
75+
}
6876
}

src/WordPress/Zip/ZipFileEntry.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
namespace WordPress\Zip;
44

55
class ZipFileEntry {
6+
7+
/**
8+
* The size of the ZIP file entry header in bytes.
9+
*
10+
* @var int
11+
*/
12+
const HEADER_SIZE = 30;
13+
614
/**
715
* @var bool
816
*/
@@ -58,7 +66,7 @@ public function __construct(
5866
int $compressionMethod,
5967
int $lastModifiedTime,
6068
int $lastModifiedDate,
61-
int $crc,
69+
$crc,
6270
int $compressedSize,
6371
int $uncompressedSize,
6472
string $path,
@@ -82,4 +90,13 @@ public function __construct(
8290
public function isFileEntry() {
8391
return true;
8492
}
93+
94+
public function size() {
95+
return (
96+
self::HEADER_SIZE +
97+
strlen($this->path) +
98+
strlen($this->extra) +
99+
$this->compressedSize
100+
);
101+
}
85102
}

0 commit comments

Comments
 (0)