Skip to content

Commit

Permalink
Adding download methods to Converters
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Oct 9, 2024
1 parent 48dae5f commit 2853be3
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 71 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ All Notable changes to `Csv` will be documented in this file

### Added

- `League\Csv\XMLConverter::download`
- `League\Csv\JsonConverter`
- `League\Csv\Constraint\Criteria::andNot`
- `League\Csv\Constraint\Criteria::orNot`
Expand Down
2 changes: 1 addition & 1 deletion docs/9.0/connections/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ To avoid breaking the flow of your application, you should create a Response obj
use League\Csv\Reader;

$reader = Reader::createFromPath('/path/to/my/file.csv', 'r');
return new Response($reader->getContent(), 200, [
return new Response($reader->toString(), 200, [
'Content-Encoding' => 'none',
'Content-Type' => 'text/csv; charset=UTF-8',
'Content-Disposition' => 'attachment; filename="name-for-your-file.csv"',
Expand Down
69 changes: 55 additions & 14 deletions docs/9.0/converter/json.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
layout: default
title: Converting a CSV into a JSON
title: Converting a collection into a JSON
---

# JSON conversion
Expand All @@ -10,8 +10,11 @@ The `JsonConverter` converts or store a collection into a JSON structure.
<p class="message-warning">Because we are building a <code>JSON</code> structure, the <code>JsonConverter</code> object
throws generic <code>SPL Exception</code> instead of <code>League\Csv\Exception</code>.</p>

To reduce memory usage, the converter transform one record at a time. This means that the class object settings are
geared toward a single element and not the whole claass,
To reduce memory usage, the converter transforms one collection element at a time. This means
that the class settings are geared toward a single element and not the whole collection.
The only pre-requisite is that each element of your collection must either be an
object implementing the `JsonSerializable` interface or a PHP structure that
can be encoded via `json_encode`.

## Settings

Expand All @@ -28,26 +31,27 @@ public JsonConverter::useFlags(int ...$flag): bool
These methods set the JSON flags to be used during conversion. The method handles all the
flags supported by PHP `json_encode` function.

If you prefer a more expressive way for setting the flags you can use `with*` and `without*` methods
If you prefer a more expressive way for setting the flags you can use the `with*` and `without*` methods
whose name are derived from PHP JSON constants.

```php
$converter = JsonConverter::create()
->addFlags(JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT)
->addFlags(JSON_PRETTY_PRINT, JSON_HEX_QUOT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT)
->removeFlags(JSON_HEX_QUOT);

//is equivalent to

$converter = JsonConverter::create()
->withPrettyPrint()
->withHexQuot()
->withUnescapedSlashes()
->withForceObject()
->withoutHexQuot();
```

<p class="message-notice">The class always uses the <code>JSON_THROW_ON_ERROR</code> to enable stop the collection
conversion in case of an error. That's why adding or removing the flag using the methods will have no effect on its
usage, the flag is <strong>ALWAYS</strong> set.</p>
<p class="message-notice">The class always uses the <code>JSON_THROW_ON_ERROR</code> flag to enable stopping the
conversion in case of an error. That's why adding or removing the flag using the methods will have no effect
on its usage, the flag is <strong>ALWAYS</strong> set.</p>

To quickly check which flags is being used, calle the `JsonConverter::useFlags` method. As for the other methods
a more expressive way exists.
Expand All @@ -62,6 +66,7 @@ $converter->useFlags(JSON_PRETTY_PRINT, JSON_HEX_QUOT); //returns false at least
$converter->usePrettyPrint(); // returns true the flag is used
$converter->useThrowOnError(); // returns true the flag is always used
$converter->useHexQuot(); // returns false the flag is not used
$converter->flags; //returns the actual flags value (as used by json_encode)
```

### Json encode depth
Expand All @@ -73,16 +78,26 @@ public JsonConverter::depth(int $depth): self
This method sets the JSON depth value during conversion. The method is a proxy to using the
`json_encode` depth parameter.

```php
$converter = JsonConverter::create()->depth(2);
$converter->depth; //returns the actual depth value (as used by json_encode)
```

### Json encode indentation

```php
public JsonConverter::indentSize(int $indentSize): self
```

This method sets the JSON indentation size value if you use the `JSON_PRETTY_PRINT` flag. In
all other situation this value stored via this method is never used. By default, the identation
all other situation this value stored via this method is never used. By default, the indentation
size is the same as in PHP (ie : 4 characters long).

```php
$converter = JsonConverter::create()->indentSize(2);
$converter->indentSize; //returns the value used
```

### Json encode formatter

```php
Expand All @@ -105,8 +120,8 @@ public JsonConverter::save(iterable $records, mixed $destination, $context = nul

The `JsonConverter::convert` accepts an `iterable` which represents the records collection
and returns a `iterable` structure lazily converted to JSON one item at a time to avoid
high memory usage. The class is built to handle large CSV documents but can be used with
small CSV document file if needed.
high memory usage. The class is built to handle large collection but can be used with
small ones if needed.

The `JsonConverter::encode` and `JsonConverter::save` methods are sugar syntactic methods to
ease storing the JSON in a file or displaying it in its full JSON string representation.
Expand All @@ -129,6 +144,9 @@ $converter = JsonConverter::create()
$row['annee'] = (int) $row['annee'];
$row['sexe'] = $row['sexe'] === 'M' ? 'male' : 'female';

//other attributes of $row are not affected
//and will be rendered as they are.

return $row;
});

Expand All @@ -154,8 +172,8 @@ This will produce the following response:
]
```

Of note, since the CSV document is not encoded in `UTF-8` we first convert it using the `CharsetConverter` class
otherwise an exception will be triggered when json encoding the data.
Of note, if your data is not encoded in `UTF-8` it will trigger a JSON exception. In our example, we first convert
The document data to `utf-8` using the `CharsetConverter` class to avoid the exception triggering.

If we wanted to store the data instead of displaying it we could do the following

Expand All @@ -164,7 +182,7 @@ If we wanted to store the data instead of displaying it we could do the followin
+ $converter->save($document, 'my/new/document.json');
```

the generated CSV will then be stored at the `my/new/document.json` path.
the generated JSON will then be stored at the `my/new/document.json` path.
The destination path can be specified using:

- a `SplFileObject` instance;
Expand All @@ -176,3 +194,26 @@ If you provide a string or a `SplFileInfo` instance:

- the file will be open using the `w` open mode.
- You can provide an additional `$context` parameter, a la `fopen`, to fine tune where and how the JSON file will be stored.

## Download

If we want to download the generated JSON the `JsonConverter::download` method can be used as follows:

```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');
JsonConverter::create()->download($reader, 'generated_file.json'); //the filename will be the name of the downloaded json!
die;
```

The caching header are left to the implementor to decide how to be set if needed. The value are left as example.

<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-notice">If you are using the package inside a framework please refer its documentation to use its recommend way.</p>
44 changes: 44 additions & 0 deletions docs/9.0/converter/xml.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,47 @@ echo htmlentities($dom->saveXML());
// </csv>
// </root>
```

## Download

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

If we want to download the generated JSON the `XMLConverter::download` method can be used as follows:

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

$reader = Reader::createFromPath('file.csv');
$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'); //the filename will be the name of the downloaded xml!
die;
```

The caching header are left to the implementor to decide how to be set if needed. The value are left as example.

<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-notice">If you are using the package inside a framework please refer its documentation to use its recommend way.</p>

By default, the method will set the encoding to `utf-8` and will not format the XML. You can set those values using
the method optional arguments.

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

$reader = Reader::createFromPath('file.csv');
$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);
die;
```

<p class="message-warning">No check is done on the validity of the <code>encoding</code> string provided.</p>
37 changes: 20 additions & 17 deletions docs/9.0/reader/record-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,20 @@ The autodiscovery feature works out of the box with public properties or argumen

the `nullable` aspect of the property is also automatically handled.

<p class="message-notice">Before version <code>9.17.0</code> the cell value must be a <code>string</code> or <code>null</code> for the feature to work.
Starting with <code>version 9.17.0</code>, the value can also be of the expected type for the property.</p>

### Improving field mapping

If the autodiscovery feature is not enough, you can complete the conversion information using the following
PHP attributes:
If the autodiscovery feature is not enough, you can complete the conversion using PHP attributes:
The following attributes are supported:

- the `League\Csv\Serializer\MapCell`
- the `League\Csv\Serializer\AfterMapping` (deprecated in 9.17.0)
- the `League\Csv\Serializer\MapRecord`
- the `League\Csv\Serializer\AfterMapping` (deprecated in 9.17.0)

<p class="message-info">The <code>AfterMapping</code> attribute is added in version <code>9.13.0</code> and deprecated in version <code>9.17.0</code></p>
<p class="message-info">The <code>MapRecord</code> attribute is added in version <code>9.17.0</code></p>
<p class="message-notice">Before version <code>9.17.0</code> the cell value must be a <code>string</code> or <code>null</code>. Starting with <code>version 9.17.0</code>, this limitation has been lifted.</p>

Here's an example of how the `League\Csv\Serializer\MapCell` attribute works:

Expand Down Expand Up @@ -171,14 +173,14 @@ The attribute can take up to five (5) arguments which are all optional:
- The `convertEmptyStringToNull` a value that can be a boolean or `null`, which control if empty string should be converted or not into the `null` value.
- The `trimFieldValueBeforeCasting` a value that can be a boolean or `null`, which control if the string should be trimmed or not before conversion.

<p class="message-info">The <code>convertEmptyStringToNull</code> argument was added in version <code>9.17.0</code></p>
<p class="message-info">The <code>ignore</code> argument was added in version <code>9.13.0</code></p>
<p class="message-info">You can use the mechanism on a CSV without a header row but it requires
adding a <code>MapCell</code> attribute on each property or method needed for the conversion. Or you
can use the optional second argument of <code>TabularDataReader::getRecordsAsObject</code> to specify the
header value, just like with <code>TabularDataReader::getRecords</code></p>
<p class="message-info">You can use the mechanism on a CSV without a header row, but it requires adding a <code>MapCell</code>
attribute on each property or method needed for the conversion. Or you can use the optional second argument of
<code>TabularDataReader::getRecordsAsObject</code> to specify the header value,
just like with <code>TabularDataReader::getRecords</code></p>
<p class="message-info">The <code>ignore</code> argument is available since version <code>9.13.0</code></p>
<p class="message-info"><code>convertEmptyStringToNull</code> argument and <code>trimFieldValueBeforeCasting</code> arguments are available since version <code>9.17.0</code></p>

In any case, if type casting fails, an exception will be thrown.
In any case, if type casting fails, an exception is thrown.

Since version `9.17.0` the `MapRecord` attribute can be used to control the full record conversion.

Expand All @@ -193,7 +195,7 @@ The attribute can take up to three (3) arguments which are all optional:

## Improving object creation

### Handling CSV cell content
### Handling string

<p class="message-info">The feature is available since version <code>9.17.0</code></p>

Expand Down Expand Up @@ -277,7 +279,7 @@ When called these methods will change the behaviour when it comes to handling em
`Denormalizer::allowEmptyStringAsNull` will convert any empty string into the `null` value
before typecasting whereas `Denormalizer::disallowEmptyStringAsNull` will preserve the value.

**Using these methods will affect the results of all conversion throughout your codebase.**
<p class="message-warning">Using these methods will affect the results of all conversion throughout your codebase.</p>

```php
use League\Csv\Reader;
Expand Down Expand Up @@ -379,7 +381,7 @@ They all support `nullable`, `mixed` as well as non-typed properties.
- They will return `null` or a specified default value, if the cell value is `null` and the type is `nullable`
- If the value can not be cast they will throw an exception.

For scalar conversion, type casting is done via PHP's `ext-filter` extension.
For scalar conversion, type casting is done using PHP's `ext-filter` extension.

<p class="message-info">Untyped properties are considered as being <code>mixed</code> type.</p>

Expand Down Expand Up @@ -481,7 +483,7 @@ Converts the cell value into a PHP `DateTimeInterface` implementing object. You
- the date format via the `format` argument
- the date timezone if needed via the `timezone` argument
- the `default` which is the default value to return if the value is `null`; should be `null` or a parsable date time `string`
- the `className` the class to use if the property is typed `mixed` or any interface that extends `DateTimeInterface`.
- the `className` the class to use if the property is typed `mixed` or any class that extends `DateTimeInterface`.

If the property is typed with:

Expand Down Expand Up @@ -559,6 +561,7 @@ The `type` option only supports scalar type (`string`, `int`, `float` and `bool`

<p class="message-info">Starting with version <code>9.17.0</code>, when using the `list` or `csv` shape you can further
trim whitespace before converting the data for each array element using the <code>trimElementValueBeforeCasting</code> option.</p>
<p class="message-info">Starting with version <code>9.17.0</code>, when the `csv` shape is used the casted array will always represent a collection of array.</p>

```php
use League\Csv\Serializer;
Expand All @@ -576,8 +579,8 @@ $stringWithSpace = 'foo , bar, baz ';

## Extending Type Casting capabilities

Three (3) mechanisms to extend typecasting are provided. You can register a callback via the `Denormalizer` class
or create a `League\Csv\Serializer\TypeCasting` implementing class. Of course, the choice will depend on your use case.
Three (3) mechanisms to extend typecasting are provided. Of course, you are free to choose the mechanism of your choice
depending on your use case.

### Registering a type using a callback

Expand Down
Loading

0 comments on commit 2853be3

Please sign in to comment.