mirror of
https://github.com/Ne-Lexa/php-zip.git
synced 2025-07-31 20:50:13 +02:00
Fixed problem with cloning a zip container.
This commit is contained in:
@@ -4,18 +4,27 @@ namespace PhpZip\Model\Data;
|
||||
|
||||
use PhpZip\Model\ZipData;
|
||||
use PhpZip\Model\ZipEntry;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Class ZipNewData.
|
||||
* The class contains a streaming resource with new content added to the ZIP archive.
|
||||
*/
|
||||
class ZipNewData implements ZipData
|
||||
{
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
/**
|
||||
* A static variable allows closing the stream in the destructor
|
||||
* only if it is its sole holder.
|
||||
*
|
||||
* @var array<int, int> array of resource ids and the number of class clones
|
||||
*/
|
||||
private static $guardClonedStream = [];
|
||||
|
||||
/** @var ZipEntry */
|
||||
private $zipEntry;
|
||||
|
||||
/** @var resource */
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* ZipStringData constructor.
|
||||
*
|
||||
@@ -38,6 +47,12 @@ class ZipNewData implements ZipData
|
||||
} elseif (\is_resource($data)) {
|
||||
$this->stream = $data;
|
||||
}
|
||||
|
||||
$resourceId = (int) $this->stream;
|
||||
self::$guardClonedStream[$resourceId] =
|
||||
isset(self::$guardClonedStream[$resourceId]) ?
|
||||
self::$guardClonedStream[$resourceId] + 1 :
|
||||
0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,8 +94,35 @@ class ZipNewData implements ZipData
|
||||
stream_copy_to_stream($stream, $outStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://php.net/manual/en/language.oop5.cloning.php
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
$resourceId = (int) $this->stream;
|
||||
self::$guardClonedStream[$resourceId] =
|
||||
isset(self::$guardClonedStream[$resourceId]) ?
|
||||
self::$guardClonedStream[$resourceId] + 1 :
|
||||
1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The stream will be closed when closing the zip archive.
|
||||
*
|
||||
* The method implements protection against closing the stream of the cloned object.
|
||||
*
|
||||
* @see ZipFile::close()
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$resourceId = (int) $this->stream;
|
||||
|
||||
if (isset(self::$guardClonedStream[$resourceId]) && self::$guardClonedStream[$resourceId] > 0) {
|
||||
self::$guardClonedStream[$resourceId]--;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (\is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
|
@@ -53,4 +53,20 @@ class ImmutableZipContainer implements \Countable
|
||||
{
|
||||
return \count($this->entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* When an object is cloned, PHP 5 will perform a shallow copy of all of the object's properties.
|
||||
* Any properties that are references to other variables, will remain references.
|
||||
* Once the cloning is complete, if a __clone() method is defined,
|
||||
* then the newly created object's __clone() method will be called, to allow any necessary properties that need to
|
||||
* be changed. NOT CALLABLE DIRECTLY.
|
||||
*
|
||||
* @see https://php.net/manual/en/language.oop5.cloning.php
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->entries as $key => $value) {
|
||||
$this->entries[$key] = clone $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,12 @@ use PhpZip\Exception\ZipException;
|
||||
*/
|
||||
class ZipContainer extends ImmutableZipContainer
|
||||
{
|
||||
/** @var ImmutableZipContainer|null */
|
||||
/**
|
||||
* @var ImmutableZipContainer|null The source container contains zip entries from
|
||||
* an open zip archive. The source container makes
|
||||
* it possible to undo changes in the archive.
|
||||
* When cloning, this container is not cloned.
|
||||
*/
|
||||
private $sourceContainer;
|
||||
|
||||
/**
|
||||
|
@@ -4,12 +4,16 @@ namespace PhpZip\Tests;
|
||||
|
||||
use PhpZip\Exception\ZipEntryNotFoundException;
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
|
||||
use PhpZip\Model\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Tests\Internal\CustomZip\ZipFileCustomWriter;
|
||||
use PhpZip\Tests\Internal\CustomZip\ZipFileWithBeforeSave;
|
||||
use PhpZip\Tests\Internal\Epub\EpubFile;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Checks the ability to create own file-type class, reader, writer and container.
|
||||
*
|
||||
* @see http://www.epubtest.org/test-books source epub files
|
||||
**.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
@@ -19,6 +23,8 @@ final class CustomZipFormatTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* @throws ZipException
|
||||
*
|
||||
* @see http://www.epubtest.org/test-books source epub files
|
||||
*/
|
||||
public function testEpub()
|
||||
{
|
||||
@@ -57,4 +63,64 @@ final class CustomZipFormatTest extends ZipTestCase
|
||||
self::assertTrue($epubFile->hasEntry('mimetype'));
|
||||
$epubFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testBeforeSaveInZipWriter()
|
||||
{
|
||||
$zipFile = new ZipFileCustomWriter();
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$zipFile['file ' . $i] = 'contents file ' . $i;
|
||||
}
|
||||
$this->existsExtraFields($zipFile, false);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$this->existsExtraFields($zipFile, false);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->existsExtraFields($zipFile, true);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testBeforeSaveInZipFile()
|
||||
{
|
||||
$zipFile = new ZipFileWithBeforeSave();
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$zipFile['file ' . $i] = 'contents file ' . $i;
|
||||
}
|
||||
$this->existsExtraFields($zipFile, false);
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
$this->existsExtraFields($zipFile, true);
|
||||
$zipFile->close();
|
||||
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
$this->existsExtraFields($zipFile, true);
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ZipFile $zipFile
|
||||
* @param bool $exists
|
||||
*/
|
||||
private function existsExtraFields(ZipFile $zipFile, $exists)
|
||||
{
|
||||
foreach ($zipFile->getEntries() as $entry) {
|
||||
$localExtras = $entry->getLocalExtraFields();
|
||||
$cdExtras = $entry->getCdExtraFields();
|
||||
|
||||
self::assertSame(isset($localExtras[NtfsExtraField::HEADER_ID]), $exists);
|
||||
self::assertSame(isset($cdExtras[NtfsExtraField::HEADER_ID]), $exists);
|
||||
|
||||
self::assertSame(isset($localExtras[NewUnixExtraField::HEADER_ID]), $exists);
|
||||
self::assertSame(isset($cdExtras[NewUnixExtraField::HEADER_ID]), $exists);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
tests/Internal/CustomZip/CustomZipWriter.php
Normal file
39
tests/Internal/CustomZip/CustomZipWriter.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Tests\Internal\CustomZip;
|
||||
|
||||
use PhpZip\IO\ZipWriter;
|
||||
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
|
||||
use PhpZip\Model\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\Model\ZipContainer;
|
||||
|
||||
/**
|
||||
* Class CustomZipWriter.
|
||||
*/
|
||||
class CustomZipWriter extends ZipWriter
|
||||
{
|
||||
/**
|
||||
* ZipWriter constructor.
|
||||
*
|
||||
* @param ZipContainer $container
|
||||
*/
|
||||
public function __construct(ZipContainer $container)
|
||||
{
|
||||
// dump($container);
|
||||
parent::__construct($container);
|
||||
// dd($this->zipContainer);
|
||||
}
|
||||
|
||||
protected function beforeWrite()
|
||||
{
|
||||
parent::beforeWrite();
|
||||
$now = new \DateTimeImmutable();
|
||||
$ntfsTimeExtra = NtfsExtraField::create($now, $now->modify('-1 day'), $now->modify('-10 day'));
|
||||
$unixExtra = new NewUnixExtraField();
|
||||
|
||||
foreach ($this->zipContainer->getEntries() as $entry) {
|
||||
$entry->addExtraField($ntfsTimeExtra);
|
||||
$entry->addExtraField($unixExtra);
|
||||
}
|
||||
}
|
||||
}
|
20
tests/Internal/CustomZip/ZipFileCustomWriter.php
Normal file
20
tests/Internal/CustomZip/ZipFileCustomWriter.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Tests\Internal\CustomZip;
|
||||
|
||||
use PhpZip\IO\ZipWriter;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Class ZipFileCustomWriter.
|
||||
*/
|
||||
class ZipFileCustomWriter extends ZipFile
|
||||
{
|
||||
/**
|
||||
* @return ZipWriter
|
||||
*/
|
||||
protected function createZipWriter()
|
||||
{
|
||||
return new CustomZipWriter($this->zipContainer);
|
||||
}
|
||||
}
|
28
tests/Internal/CustomZip/ZipFileWithBeforeSave.php
Normal file
28
tests/Internal/CustomZip/ZipFileWithBeforeSave.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Tests\Internal\CustomZip;
|
||||
|
||||
use PhpZip\Model\Extra\Fields\NewUnixExtraField;
|
||||
use PhpZip\Model\Extra\Fields\NtfsExtraField;
|
||||
use PhpZip\ZipFile;
|
||||
|
||||
/**
|
||||
* Class ZipFileWithBeforeSave.
|
||||
*/
|
||||
class ZipFileWithBeforeSave extends ZipFile
|
||||
{
|
||||
/**
|
||||
* Event before save or output.
|
||||
*/
|
||||
protected function onBeforeSave()
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$ntfsTimeExtra = NtfsExtraField::create($now, $now->modify('-1 day'), $now->modify('-10 day'));
|
||||
$unixExtra = new NewUnixExtraField();
|
||||
|
||||
foreach ($this->zipContainer->getEntries() as $entry) {
|
||||
$entry->addExtraField($ntfsTimeExtra);
|
||||
$entry->addExtraField($unixExtra);
|
||||
}
|
||||
}
|
||||
}
|
@@ -304,6 +304,55 @@ class ZipEntryTest extends TestCase
|
||||
static::assertNull($zipEntry->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testZipNewDataGuardClone()
|
||||
{
|
||||
$resource = fopen('php://temp', 'r+b');
|
||||
static::assertNotFalse($resource);
|
||||
fwrite($resource, random_bytes(1024));
|
||||
rewind($resource);
|
||||
|
||||
$zipEntry = new ZipEntry('entry');
|
||||
$zipEntry2 = new ZipEntry('entry2');
|
||||
|
||||
$zipData = new ZipNewData($zipEntry, $resource);
|
||||
$zipData2 = new ZipNewData($zipEntry2, $resource);
|
||||
$cloneData = clone $zipData;
|
||||
$cloneData2 = clone $cloneData;
|
||||
|
||||
static::assertSame($zipData->getDataAsStream(), $resource);
|
||||
static::assertSame($zipData2->getDataAsStream(), $resource);
|
||||
static::assertSame($cloneData->getDataAsStream(), $resource);
|
||||
static::assertSame($cloneData2->getDataAsStream(), $resource);
|
||||
|
||||
$validResource = \is_resource($resource);
|
||||
static::assertTrue($validResource);
|
||||
|
||||
unset($cloneData);
|
||||
$validResource = \is_resource($resource);
|
||||
static::assertTrue($validResource);
|
||||
|
||||
unset($zipData);
|
||||
$validResource = \is_resource($resource);
|
||||
static::assertTrue($validResource);
|
||||
|
||||
unset($zipData2);
|
||||
$validResource = \is_resource($resource);
|
||||
static::assertTrue($validResource);
|
||||
|
||||
$reflectionClass = new \ReflectionClass($cloneData2);
|
||||
static::assertSame(
|
||||
$reflectionClass->getStaticProperties()['guardClonedStream'][(int) $resource],
|
||||
0
|
||||
);
|
||||
|
||||
unset($cloneData2);
|
||||
$validResource = \is_resource($resource);
|
||||
static::assertFalse($validResource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider providePlatform
|
||||
*
|
||||
|
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace PhpZip\Tests;
|
||||
|
||||
use PhpZip\Exception\ZipException;
|
||||
use PhpZip\Tests\Internal\ZipFileExtended;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @small
|
||||
*/
|
||||
class ZipEventTest extends ZipTestCase
|
||||
{
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testBeforeSave()
|
||||
{
|
||||
$zipFile = new ZipFileExtended();
|
||||
$zipFile->openFile(__DIR__ . '/resources/apk.zip');
|
||||
static::assertTrue(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertTrue(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertTrue(isset($zipFile['META-INF/CERT.RSA']));
|
||||
// the "META-INF/" folder will be deleted when saved
|
||||
// in the ZipFileExtended::onBeforeSave() method
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
|
||||
static::assertCorrectZipArchive($this->outputFilename);
|
||||
|
||||
$zipFile->openFile($this->outputFilename);
|
||||
static::assertFalse(isset($zipFile['META-INF/MANIFEST.MF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.SF']));
|
||||
static::assertFalse(isset($zipFile['META-INF/CERT.RSA']));
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
@@ -2435,4 +2435,18 @@ class ZipFileTest extends ZipTestCase
|
||||
|
||||
$zipFile->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZipException
|
||||
*/
|
||||
public function testMultiSave()
|
||||
{
|
||||
$zipFile = new ZipFile();
|
||||
$zipFile['file 1'] = 'contents';
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$zipFile->saveAsFile($this->outputFilename);
|
||||
self::assertCorrectZipArchive($this->outputFilename);
|
||||
}
|
||||
$zipFile->close();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user