Skip to content

Commit

Permalink
Normalizes the download method is the package
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Oct 14, 2024
1 parent 6b2e346 commit e3e2135
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 32 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ All Notable changes to `Csv` will be documented in this file
### Added

- `League\Csv\JsonConverter::chunkSize`
- `League\Csv\AbstractCsv::download`

### Deprecated

- None
- `League\Csv\AbstractCsv::output` use `League\Csv\AbstractCsv::download` instead

### Fixed

- None
- `League\Csv\JsonConverter::download` the filename is now nullable
- `League\Csv\XMLConverter::download` the filename is now nullable

### Remove

Expand Down
10 changes: 5 additions & 5 deletions docs/9.0/connections/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,12 @@ echo $csv->getContent();

## Downloading the document

<p class="message-warning">starting with version <code>9.18.0</code>, the <code>download</code> method replaces the <code>output</code> method which is deprecated.</p>

To make your CSV document downloadable use the `output` method to force the use of the output buffer on the CSV content.

```php
public AbstractCsv::output(string $filename = null): int
public AbstractCsv::download(?string $filename = null): int
```

The method returns the number of characters read from the handle and passed through to the output.
Expand All @@ -86,7 +88,7 @@ header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="name-for-your-file.csv"');

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
$reader->output();
$reader->download();
die;
```

Expand All @@ -96,14 +98,12 @@ die;
use League\Csv\Reader;

$reader = Reader::createFromPath('file.csv');
$reader->output('name-for-your-file.csv');
$reader->download('name-for-your-file.csv');
die;
```

<p class="message-notice">If you just need to make the CSV downloadable, end your script with a call to <code>die</code> just after the <code>output</code> method. You <strong>should not</strong> return the method returned value.</p>

<p class="message-warning">starting with version <code>9.1.0</code>, the <code>output</code> method will throw an <code>Exception</code> if the provided <code>$filename</code> does not comply with <a href="https://tools.ietf.org/html/rfc6266#section-4">RFC6266</a></p>

## Outputting the document into chunks

```php
Expand Down
33 changes: 30 additions & 3 deletions docs/9.0/converter/json.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,36 @@ If you provide a string or a `SplFileInfo` instance:

## Download

To download the generated JSON on the fly you can use the `JsonConverter::download` method:
<p class="message-warning">If you are using the package inside a framework please use the framework recommended way instead of the describe mechanism hereafter.</p>

To download the generated JSON you can use the `JsonConverter::download` method. The method returns
the total number of bytes sent just like the `JsonConverter::save` method and enable downloading the JSON on the fly.

### General purpose

```php
use League\Csv\Reader;
use League\Csv\JsonConverter;

$reader = Reader::createFromPath('file.csv');
$reader->setHeaderOffset(0);

header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
header('Content-Type: application/json; charset=UTF-8');
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="name-for-your-file.json"');

JsonConverter::create()->download($reader);
die;
```

In this scenario, you have to specify all the headers for the file to be downloaded.

### Using a filename

If you want to reduce the number of headers to write you can specify the downloaded filename.

```php
use League\Csv\Reader;
Expand All @@ -233,6 +262,4 @@ JsonConverter::create()->download($reader, 'generated_file.json');
die;
```

<p class="message-info">the <code>download</code> method returns the total number of bytes sent like the <code>save</code> method.</p>
<p class="message-info">If you are using the package inside a framework please use the framework recommended way instead of the <code>download</code> method.</p>
<p class="message-notice">The caching headers are given as an example for using additional headers, it is up to the user to decide if those headers are needed or not.</p>
42 changes: 38 additions & 4 deletions docs/9.0/converter/xml.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,37 @@ echo htmlentities($dom->saveXML());

## Download

<p class="message-warning">If you are using the package inside a framework please use the framework recommended way instead of the describe mechanism hereafter.</p>

To download the generated JSON you can use the `XMLConverter::download` method. The method returns
the total number of bytes sent just like the `XMLConverter::save` method and enable downloading the XML on the fly.

### General purpose

<p class="message-info">new in version <code>9.18.0</code></p>

```php
use League\Csv\Reader;
use League\Csv\JsonConverter;

$reader = Reader::createFromPath('file.csv');
$reader->setHeaderOffset(0);

header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
header('Content-Type: text/xml; charset=UTF-8');
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename="name-for-your-file.xml"');

XMLConverter::create()->download($reader);
die;
```

In this scenario, you have to specify all the headers for the file to be downloaded.

### Using a filename

<p class="message-info">new in version <code>9.17.0</code></p>

To download the generated XML on the fly you can use the `XMLConverter::download` method:
Expand All @@ -196,12 +227,10 @@ header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
//the filename will be the name of the downloaded xml as shown by your HTTP client!
XMLConverter::create()->download($reader, 'generated_file.xml');
XMLConverter::create()->download($reader, 'name-for-your-file.xml');
die;
```

<p class="message-info">the <code>download</code> method returns the total number of bytes sent.</p>
<p class="message-info">If you are using the package inside a framework please use the framework recommended way instead of the <code>download</code> method.</p>
<p class="message-notice">The caching headers are given as an example for using additional headers, it is up to the user to decide if those headers are needed or not.</p>

By default, the method will set the encoding to `utf-8` and will not format the XML. You can set those values using
Expand All @@ -217,7 +246,12 @@ $reader->setHeaderOffset(0);
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
header('Expires: 0');
XMLConverter::create()->download($reader, 'generated_file.xml', encoding: 'iso-8859-1', formatOutput: true);
XMLConverter::create()->download(
records: $reader,
filename: 'generated_file.xml',
encoding: 'iso-8859-1',
formatOutput: true,
);
die;
```

Expand Down
33 changes: 25 additions & 8 deletions src/AbstractCsv.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,16 +242,12 @@ public function toString(): string
*
* Returns the number of characters read from the handle and passed through to the output.
*
* @throws Exception
* @throws InvalidArgumentException|Exception
*/
public function output(?string $filename = null): int
public function download(?string $filename = null): int
{
if (null !== $filename) {
try {
HttpHeaders::forFileDownload($filename, 'text/csv');
} catch (InvalidArgumentException $exception) {
throw new InvalidArgument($exception->getMessage());
}
HttpHeaders::forFileDownload($filename, 'text/csv');
}

$this->document->rewind();
Expand All @@ -262,7 +258,6 @@ public function output(?string $filename = null): int

$stream = Stream::createFromString($this->getOutputBOM());
$stream->rewind();

$res1 = $stream->fpassthru();
if (false === $res1) {
throw new RuntimeException('Unable to output the document.');
Expand Down Expand Up @@ -492,4 +487,26 @@ protected function sendHeaders(string $filename): void
header('Content-Description: File Transfer');
header('Content-Disposition: '.$disposition);
}

/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @codeCoverageIgnore
* @deprecated since version 9.18.0
* @see AbstractCsv::download()
*
* Outputs all data on the CSV file.
*
* Returns the number of characters read from the handle and passed through to the output.
*
* @throws Exception
*/
public function output(?string $filename = null): int
{
try {
return $this->download($filename);
} catch (InvalidArgumentException $exception) {
throw new InvalidArgument($exception->getMessage());
}
}
}
13 changes: 7 additions & 6 deletions src/AbstractCsvTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace League\Csv;

use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
Expand Down Expand Up @@ -110,16 +111,16 @@ public function testCloningIsForbidden(): void
public function testOutputSize(): void
{
ob_start();
$length = $this->csv->output('test.csv');
$length = $this->csv->download('test.csv');
ob_end_clean();
self::assertSame(60, $length);
}

public function testInvalidOutputFile(): void
{
$this->expectException(InvalidArgument::class);
$this->expectException(InvalidArgumentException::class);

$this->csv->output('invalid/file.csv');
$this->csv->download('invalid/file.csv');
}

public function testOutputHeaders(): void
Expand All @@ -131,7 +132,7 @@ public function testOutputHeaders(): void
$raw_csv = Bom::Utf8->value."john,doe,[email protected]\njane,doe,[email protected]\n";
$csv = Reader::createFromString($raw_csv);
ob_start();
$csv->output('tést.csv');
$csv->download('tést.csv');
ob_end_clean();
$headers = xdebug_get_headers();

Expand Down Expand Up @@ -457,7 +458,7 @@ public function testOutputStripBOM(): void
$csv->setOutputBOM(Bom::Utf16Be->value);

ob_start();
$csv->output();
$csv->download();
/** @var string $result */
$result = ob_get_clean();

Expand All @@ -473,7 +474,7 @@ public function testOutputDoesNotStripBOM(): void
$csv->includeInputBOM();

ob_start();
$csv->output();
$csv->download();
/** @var string $result */
$result = ob_get_clean();

Expand Down
6 changes: 4 additions & 2 deletions src/JsonConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,11 @@ public function formatter(?Closure $formatter): self
* @throws Exception
* @throws JsonException
*/
public function download(iterable $records, string $filename): int
public function download(iterable $records, ?string $filename = null): int
{
HttpHeaders::forFileDownload($filename, 'application/json');
if (null !== $filename) {
HttpHeaders::forFileDownload($filename, 'application/json; charset=utf-8');
}

return $this->save($records, new SplFileObject('php://output', 'w'));
}
Expand Down
6 changes: 4 additions & 2 deletions src/XMLConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ public function convert(iterable $records): DOMDocument
*
* @throws Exception
*/
public function download(iterable $records, string $filename, string $encoding = 'utf-8', bool $formatOutput = false): int|false
public function download(iterable $records, ?string $filename = null, string $encoding = 'utf-8', bool $formatOutput = false): int|false
{
$document = $this->convert($records);
$document->encoding = $encoding;
$document->formatOutput = $formatOutput;

HttpHeaders::forFileDownload($filename, 'application/xml');
if (null !== $filename) {
HttpHeaders::forFileDownload($filename, 'application/xml; charset='.strtolower($encoding));
}

return $document->save('php://output');
}
Expand Down

0 comments on commit e3e2135

Please sign in to comment.