1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-01-16 23:08:14 +01:00

added method outputAsSymfonyResponse, rename method outputAsResponse to outputAsPsr7Response

This commit is contained in:
Ne-Lexa 2021-02-24 15:37:11 +03:00
parent e8418d57b1
commit a11c6367b4
6 changed files with 273 additions and 78 deletions

View File

@ -65,6 +65,12 @@ namespace PHPSTORM_META {
expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 2, argumentsSet("zip_mime_types")); expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 2, argumentsSet("zip_mime_types"));
expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 3, argumentsSet("bool")); expectedArguments(\PhpZip\ZipFile::outputAsResponse(), 3, argumentsSet("bool"));
expectedArguments(\PhpZip\ZipFile::outputAsPsr7Response(), 2, argumentsSet("zip_mime_types"));
expectedArguments(\PhpZip\ZipFile::outputAsPsr7Response(), 3, argumentsSet("bool"));
expectedArguments(\PhpZip\ZipFile::outputAsSymfonyResponse(), 1, argumentsSet("zip_mime_types"));
expectedArguments(\PhpZip\ZipFile::outputAsSymfonyResponse(), 2, argumentsSet("bool"));
registerArgumentsSet( registerArgumentsSet(
'dos_charset', 'dos_charset',
\PhpZip\Constants\DosCodePage::CP_LATIN_US, \PhpZip\Constants\DosCodePage::CP_LATIN_US,

View File

@ -144,7 +144,8 @@ finally{
- [ZipFile::openFromString](#zipfileopenfromstring) - открывает ZIP-архив из строки. - [ZipFile::openFromString](#zipfileopenfromstring) - открывает ZIP-архив из строки.
- [ZipFile::openFromStream](#zipfileopenfromstream) - открывает ZIP-архив из потока. - [ZipFile::openFromStream](#zipfileopenfromstream) - открывает ZIP-архив из потока.
- [ZipFile::outputAsAttachment](#zipfileoutputasattachment) - выводит ZIP-архив в браузер. - [ZipFile::outputAsAttachment](#zipfileoutputasattachment) - выводит ZIP-архив в браузер.
- [ZipFile::outputAsResponse](#zipfileoutputasresponse) - выводит ZIP-архив, как Response PSR-7. - [ZipFile::outputAsPsr7Response](#zipfileoutputaspsr7response) - выводит ZIP-архив, как PSR-7 Response.
- [ZipFile::outputAsSymfonyResponse](#zipfileoutputassymfonyresponse) - выводит ZIP-архив, как Symfony Response.
- [ZipFile::outputAsString](#zipfileoutputasstring) - выводит ZIP-архив в виде строки. - [ZipFile::outputAsString](#zipfileoutputasstring) - выводит ZIP-архив в виде строки.
- [ZipFile::rename](#zipfilerename) - переименовывает запись по имени. - [ZipFile::rename](#zipfilerename) - переименовывает запись по имени.
- [ZipFile::rewrite](#zipfilerewrite) - сохраняет изменения и заново открывает изменившийся архив. - [ZipFile::rewrite](#zipfilerewrite) - сохраняет изменения и заново открывает изменившийся архив.
@ -753,28 +754,57 @@ $zipFile->outputAsAttachment($outputFilename);
$mimeType = 'application/zip'; $mimeType = 'application/zip';
$zipFile->outputAsAttachment($outputFilename, $mimeType); $zipFile->outputAsAttachment($outputFilename, $mimeType);
``` ```
##### ZipFile::outputAsResponse ##### ZipFile::outputAsPsr7Response
Выводит ZIP-архив, как Response [PSR-7](http://www.php-fig.org/psr/psr-7/). Выводит ZIP-архив, как [PSR-7 Response](http://www.php-fig.org/psr/psr-7/).
Метод вывода может использоваться в любом PSR-7 совместимом фреймворке. Метод вывода может использоваться в любом PSR-7 совместимом фреймворке.
```php ```php
// $response = ....; // instance Psr\Http\Message\ResponseInterface // $response = ....; // instance Psr\Http\Message\ResponseInterface
$zipFile->outputAsResponse($response, $outputFilename); $zipFile->outputAsPsr7Response($response, $outputFilename);
``` ```
Можно установить MIME-тип: Можно установить MIME-тип:
```php ```php
$mimeType = 'application/zip'; $mimeType = 'application/zip';
$zipFile->outputAsResponse($response, $outputFilename, $mimeType); $zipFile->outputAsPsr7Response($response, $outputFilename, $mimeType);
``` ```
Пример для Slim Framework: ##### ZipFile::outputAsSymfonyResponse
Выводит ZIP-архив, как [Symfony Response](https://symfony.com/doc/current/components/http_foundation.html#response).
Метод вывода можно использовать в фреймворке Symfony.
```php ```php
$app = new \Slim\App; $response = $zipFile->outputAsSymfonyResponse($outputFilename);
$app->get('/download', function ($req, $res, $args) { ```
$zipFile = new \PhpZip\ZipFile(); Вы можете установить Mime-Type:
$zipFile['file.txt'] = 'content'; ```php
return $zipFile->outputAsResponse($res, 'file.zip'); $mimeType = 'application/zip';
}); $response = $zipFile->outputAsSymfonyResponse($outputFilename, $mimeType);
$app->run(); ```
Пример использования в Symfony Controller:
```php
<?php
namespace App\Controller;
use PhpZip\ZipFile;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DownloadZipController
{
/**
* @Route("/downloads/{id}")
*
* @throws \PhpZip\Exception\ZipException
*/
public function __invoke(string $id): Response
{
$zipFile = new ZipFile();
$zipFile['file'] = 'contents';
$outputFilename = $id . '.zip';
return $zipFile->outputAsSymfonyResponse($outputFilename);
}
}
``` ```
##### ZipFile::rewrite ##### ZipFile::rewrite
Сохраняет изменения и заново открывает изменившийся архив. Сохраняет изменения и заново открывает изменившийся архив.

View File

@ -149,7 +149,8 @@ Other examples can be found in the `tests/` folder
- [ZipFile::openFromString](#zipfileopenfromstring) - opens a zip-archive from a string. - [ZipFile::openFromString](#zipfileopenfromstring) - opens a zip-archive from a string.
- [ZipFile::openFromStream](#zipfileopenfromstream) - opens a zip-archive from the stream. - [ZipFile::openFromStream](#zipfileopenfromstream) - opens a zip-archive from the stream.
- [ZipFile::outputAsAttachment](#zipfileoutputasattachment) - outputs a ZIP-archive to the browser. - [ZipFile::outputAsAttachment](#zipfileoutputasattachment) - outputs a ZIP-archive to the browser.
- [ZipFile::outputAsResponse](#zipfileoutputasresponse) - outputs a ZIP-archive as PSR-7 Response. - [ZipFile::outputAsPsr7Response](#zipfileoutputaspsr7response) - outputs a ZIP-archive as PSR-7 Response.
- [ZipFile::outputAsSymfonyResponse](#zipfileoutputaspsr7response) - outputs a ZIP-archive as Symfony Response.
- [ZipFile::outputAsString](#zipfileoutputasstring) - outputs a ZIP-archive as string. - [ZipFile::outputAsString](#zipfileoutputasstring) - outputs a ZIP-archive as string.
- [ZipFile::rename](#zipfilerename) - renames an entry defined by its name. - [ZipFile::rename](#zipfilerename) - renames an entry defined by its name.
- [ZipFile::rewrite](#zipfilerewrite) - save changes and re-open the changed archive. - [ZipFile::rewrite](#zipfilerewrite) - save changes and re-open the changed archive.
@ -782,18 +783,57 @@ You can set the Mime-Type:
$mimeType = 'application/zip'; $mimeType = 'application/zip';
$zipFile->outputAsAttachment($outputFilename, $mimeType); $zipFile->outputAsAttachment($outputFilename, $mimeType);
``` ```
##### ZipFile::outputAsResponse ##### ZipFile::outputAsPsr7Response
Outputs a ZIP-archive as [PSR-7 Response](http://www.php-fig.org/psr/psr-7/). Outputs a ZIP-archive as [PSR-7 Response](http://www.php-fig.org/psr/psr-7/).
The output method can be used in any PSR-7 compatible framework. The output method can be used in any PSR-7 compatible framework.
```php ```php
// $response = ....; // instance Psr\Http\Message\ResponseInterface // $response = ....; // instance Psr\Http\Message\ResponseInterface
$zipFile->outputAsResponse($response, $outputFilename); $zipFile->outputAsPsr7Response($response, $outputFilename);
``` ```
You can set the Mime-Type: You can set the Mime-Type:
```php ```php
$mimeType = 'application/zip'; $mimeType = 'application/zip';
$zipFile->outputAsResponse($response, $outputFilename, $mimeType); $zipFile->outputAsPsr7Response($response, $outputFilename, $mimeType);
```
##### ZipFile::outputAsSymfonyResponse
Outputs a ZIP-archive as [Symfony Response](https://symfony.com/doc/current/components/http_foundation.html#response).
The output method can be used in Symfony framework.
```php
$response = $zipFile->outputAsSymfonyResponse($outputFilename);
```
You can set the Mime-Type:
```php
$mimeType = 'application/zip';
$response = $zipFile->outputAsSymfonyResponse($outputFilename, $mimeType);
```
Example use in Symfony Controller:
```php
<?php
namespace App\Controller;
use PhpZip\ZipFile;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DownloadZipController
{
/**
* @Route("/downloads/{id}")
*
* @throws \PhpZip\Exception\ZipException
*/
public function __invoke(string $id): Response
{
$zipFile = new ZipFile();
$zipFile['file'] = 'contents';
$outputFilename = $id . '.zip';
return $zipFile->outputAsSymfonyResponse($outputFilename);
}
}
``` ```
##### ZipFile::rewrite ##### ZipFile::rewrite
Save changes and re-open the changed archive. Save changes and re-open the changed archive.

View File

@ -36,7 +36,8 @@
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",
"symfony/var-dumper": "^5.0", "symfony/var-dumper": "^5.0",
"friendsofphp/php-cs-fixer": "^2.18", "friendsofphp/php-cs-fixer": "^2.18",
"vimeo/psalm": "^4.6" "vimeo/psalm": "^4.6",
"symfony/http-foundation": "^5.2"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -35,6 +35,8 @@ use PhpZip\Util\StringUtil;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo; use Symfony\Component\Finder\SplFileInfo as SymfonySplFileInfo;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
/** /**
* Create, open .ZIP files, modify, get info and extract files. * Create, open .ZIP files, modify, get info and extract files.
@ -239,8 +241,8 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* *
* @param ?string $comment * @param ?string $comment
* *
* @throws ZipException
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
* @throws ZipException
* *
* @return ZipFile * @return ZipFile
*/ */
@ -269,8 +271,8 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
} }
/** /**
* @throws ZipException
* @throws ZipEntryNotFoundException * @throws ZipEntryNotFoundException
* @throws ZipException
* *
* @return resource * @return resource
*/ */
@ -313,8 +315,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* *
* @return ZipFile * @return ZipFile
*/ */
public function extractTo(string $destDir, $entries = null, array $options = [], ?array &$extractedEntries = []): self public function extractTo(
{ string $destDir,
$entries = null,
array $options = [],
?array &$extractedEntries = []
): self {
if (!file_exists($destDir)) { if (!file_exists($destDir)) {
throw new ZipException(sprintf('Destination %s not found', $destDir)); throw new ZipException(sprintf('Destination %s not found', $destDir));
} }
@ -942,8 +948,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* @return ZipFile * @return ZipFile
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/ */
public function addFilesFromGlob(string $inputDir, string $globPattern, string $localPath = '/', ?int $compressionMethod = null): self public function addFilesFromGlob(
{ string $inputDir,
string $globPattern,
string $localPath = '/',
?int $compressionMethod = null
): self {
return $this->addGlob($inputDir, $globPattern, $localPath, false, $compressionMethod); return $this->addGlob($inputDir, $globPattern, $localPath, false, $compressionMethod);
} }
@ -1016,8 +1026,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* @return ZipFile * @return ZipFile
* @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax * @sse https://en.wikipedia.org/wiki/Glob_(programming) Glob pattern syntax
*/ */
public function addFilesFromGlobRecursive(string $inputDir, string $globPattern, string $localPath = '/', ?int $compressionMethod = null): self public function addFilesFromGlobRecursive(
{ string $inputDir,
string $globPattern,
string $localPath = '/',
?int $compressionMethod = null
): self {
return $this->addGlob($inputDir, $globPattern, $localPath, true, $compressionMethod); return $this->addGlob($inputDir, $globPattern, $localPath, true, $compressionMethod);
} }
@ -1039,8 +1053,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* *
* @internal param bool $recursive Recursive search * @internal param bool $recursive Recursive search
*/ */
public function addFilesFromRegex(string $inputDir, string $regexPattern, string $localPath = '/', ?int $compressionMethod = null): self public function addFilesFromRegex(
{ string $inputDir,
string $regexPattern,
string $localPath = '/',
?int $compressionMethod = null
): self {
return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod); return $this->addRegex($inputDir, $regexPattern, $localPath, false, $compressionMethod);
} }
@ -1097,8 +1115,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* *
* @throws ZipException * @throws ZipException
*/ */
private function doAddFiles(string $fileSystemDir, array $files, string $zipPath, ?int $compressionMethod = null): void private function doAddFiles(
{ string $fileSystemDir,
array $files,
string $zipPath,
?int $compressionMethod = null
): void {
$fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR; $fileSystemDir = rtrim($fileSystemDir, '/\\') . \DIRECTORY_SEPARATOR;
if (!empty($zipPath)) { if (!empty($zipPath)) {
@ -1140,8 +1162,12 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* *
* @internal param bool $recursive Recursive search * @internal param bool $recursive Recursive search
*/ */
public function addFilesFromRegexRecursive(string $inputDir, string $regexPattern, string $localPath = '/', ?int $compressionMethod = null): self public function addFilesFromRegexRecursive(
{ string $inputDir,
string $regexPattern,
string $localPath = '/',
?int $compressionMethod = null
): self {
return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod); return $this->addRegex($inputDir, $regexPattern, $localPath, true, $compressionMethod);
} }
@ -1530,10 +1556,35 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
*/ */
public function outputAsAttachment(string $outputFilename, ?string $mimeType = null, bool $attachment = true): void public function outputAsAttachment(string $outputFilename, ?string $mimeType = null, bool $attachment = true): void
{ {
if ($mimeType === null) { [
$mimeType = $this->getMimeTypeByFilename($outputFilename); 'resource' => $resource,
'headers' => $headers,
] = $this->getOutputData($outputFilename, $mimeType, $attachment);
if (!headers_sent()) {
foreach ($headers as $key => $value) {
header($key . ': ' . $value);
}
} }
rewind($resource);
try {
echo stream_get_contents($resource, -1, 0);
} finally {
fclose($resource);
}
}
/**
* @param ?string $mimeType
*
* @throws ZipException
*/
private function getOutputData(string $outputFilename, ?string $mimeType = null, bool $attachment = true): array
{
$mimeType ??= $this->getMimeTypeByFilename($outputFilename);
if (!($handle = fopen('php://temp', 'w+b'))) { if (!($handle = fopen('php://temp', 'w+b'))) {
throw new InvalidArgumentException('php://temp cannot open for write.'); throw new InvalidArgumentException('php://temp cannot open for write.');
} }
@ -1542,23 +1593,21 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
$size = fstat($handle)['size']; $size = fstat($handle)['size'];
$headerContentDisposition = 'Content-Disposition: ' . ($attachment ? 'attachment' : 'inline'); $contentDisposition = $attachment ? 'attachment' : 'inline';
$name = basename($outputFilename);
if (!empty($outputFilename)) { if (!empty($name)) {
$headerContentDisposition .= '; filename="' . basename($outputFilename) . '"'; $contentDisposition .= '; filename="' . $name . '"';
} }
header($headerContentDisposition); return [
header('Content-Type: ' . $mimeType); 'resource' => $handle,
header('Content-Length: ' . $size); 'headers' => [
'Content-Disposition' => $contentDisposition,
rewind($handle); 'Content-Type' => $mimeType,
'Content-Length' => $size,
try { ],
echo stream_get_contents($handle, -1, 0); ];
} finally {
fclose($handle);
}
} }
protected function getMimeTypeByFilename(string $outputFilename): string protected function getMimeTypeByFilename(string $outputFilename): string
@ -1581,39 +1630,93 @@ class ZipFile implements \Countable, \ArrayAccess, \Iterator
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline * @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
* *
* @throws ZipException * @throws ZipException
*
* @deprecated deprecated since version 2.0, replace to {@see ZipFile::outputAsPsr7Response}
*/ */
public function outputAsResponse(ResponseInterface $response, string $outputFilename, ?string $mimeType = null, bool $attachment = true): ResponseInterface public function outputAsResponse(
{ ResponseInterface $response,
if ($mimeType === null) { string $outputFilename,
$mimeType = $this->getMimeTypeByFilename($outputFilename); ?string $mimeType = null,
} bool $attachment = true
): ResponseInterface {
@trigger_error(
sprintf(
'Method %s is deprecated. Replace to %s::%s',
__METHOD__,
__CLASS__,
'outputAsPsr7Response'
),
\E_USER_DEPRECATED
);
if (!($handle = fopen('php://temp', 'w+b'))) { return $this->outputAsPsr7Response($response, $outputFilename, $mimeType, $attachment);
throw new InvalidArgumentException('php://temp cannot open for write.'); }
}
$this->writeZipToStream($handle);
$this->close();
rewind($handle);
$contentDispositionValue = ($attachment ? 'attachment' : 'inline'); /**
* Output .ZIP archive as PSR-7 Response.
*
* @param ResponseInterface $response Instance PSR-7 Response
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
*
* @throws ZipException
*
* @since 4.0.0
*/
public function outputAsPsr7Response(
ResponseInterface $response,
string $outputFilename,
?string $mimeType = null,
bool $attachment = true
): ResponseInterface {
[
'resource' => $resource,
'headers' => $headers,
] = $this->getOutputData($outputFilename, $mimeType, $attachment);
if (!empty($outputFilename)) { foreach ($headers as $key => $value) {
$contentDispositionValue .= '; filename="' . basename($outputFilename) . '"';
}
$stream = new ResponseStream($handle);
$size = $stream->getSize();
if ($size !== null) {
/** @noinspection CallableParameterUseCaseInTypeContextInspection */ /** @noinspection CallableParameterUseCaseInTypeContextInspection */
$response = $response->withHeader('Content-Length', (string) $size); $response = $response->withHeader($key, (string) $value);
} }
return $response return $response->withBody(new ResponseStream($resource));
->withHeader('Content-Type', $mimeType) }
->withHeader('Content-Disposition', $contentDispositionValue)
->withBody($stream) /**
; * Output .ZIP archive as Symfony Response.
*
* @param string $outputFilename Output filename
* @param string|null $mimeType Mime-Type
* @param bool $attachment Http Header 'Content-Disposition' if true then attachment otherwise inline
*
* @throws ZipException
*
* @since 4.0.0
*/
public function outputAsSymfonyResponse(
string $outputFilename,
?string $mimeType = null,
bool $attachment = true
): Response {
[
'resource' => $resource,
'headers' => $headers,
] = $this->getOutputData($outputFilename, $mimeType, $attachment);
return new StreamedResponse(
static function () use ($resource): void {
if (!($output = fopen('php://output', 'w+b'))) {
throw new InvalidArgumentException('php://output cannot open for write.');
}
rewind($resource);
stream_copy_to_stream($resource, $output);
fclose($output);
fclose($resource);
},
200,
$headers
);
} }
/** /**

View File

@ -1856,7 +1856,7 @@ class ZipFileTest extends ZipTestCase
public function testFilename0(): void public function testFilename0(): void
{ {
$zipFile = new ZipFile(); $zipFile = new ZipFile();
$zipFile[0] = 0; $zipFile[0] = '0';
static::assertTrue(isset($zipFile['0'])); static::assertTrue(isset($zipFile['0']));
static::assertCount(1, $zipFile); static::assertCount(1, $zipFile);
$zipFile $zipFile
@ -1891,18 +1891,33 @@ class ZipFileTest extends ZipTestCase
/** /**
* @throws ZipException * @throws ZipException
*/ */
public function testPsrResponse(): void public function testOutputAsPsr7Response(): void
{ {
$zipFile = new ZipFile(); $zipFile = new ZipFile();
for ($i = 0; $i < 10; $i++) { for ($i = 0; $i < 10; $i++) {
$zipFile[$i] = $i; $zipFile[$i] = (string) $i;
} }
$filename = 'file.jar'; $filename = 'file.jar';
$response = $zipFile->outputAsResponse(new Response(), $filename); $response = $zipFile->outputAsPsr7Response(new Response(), $filename);
static::assertSame('application/java-archive', $response->getHeaderLine('content-type')); static::assertSame('application/java-archive', $response->getHeaderLine('content-type'));
static::assertSame('attachment; filename="file.jar"', $response->getHeaderLine('content-disposition')); static::assertSame('attachment; filename="file.jar"', $response->getHeaderLine('content-disposition'));
} }
/**
* @throws ZipException
*/
public function testOutputAsSymfonyResponse(): void
{
$zipFile = new ZipFile();
for ($i = 0; $i < 10; $i++) {
$zipFile[$i] = (string) $i;
}
$filename = 'file.jar';
$response = $zipFile->outputAsSymfonyResponse($filename);
static::assertSame('application/java-archive', $response->headers->get('content-type'));
static::assertSame('attachment; filename="file.jar"', $response->headers->get('content-disposition'));
}
/** /**
* @dataProvider provideCompressionLevels * @dataProvider provideCompressionLevels
* *