Skip to content

Commit 51260dc

Browse files
committed
Adding the EnumFormatter
1 parent b092e2a commit 51260dc

6 files changed

Lines changed: 324 additions & 5 deletions

File tree

composer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@
3232
"require-dev": {
3333
"ext-dom": "*",
3434
"ext-xdebug": "*",
35-
"friendsofphp/php-cs-fixer": "^3.75.0",
36-
"phpbench/phpbench": "^1.4.1",
37-
"phpstan/phpstan": "^1.12.27",
35+
"friendsofphp/php-cs-fixer": "^3.92.3",
36+
"phpbench/phpbench": "^1.4.3",
37+
"phpstan/phpstan": "^1.12.32",
3838
"phpstan/phpstan-deprecation-rules": "^1.2.1",
3939
"phpstan/phpstan-phpunit": "^1.4.2",
4040
"phpstan/phpstan-strict-rules": "^1.6.2",
41-
"phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.3.6",
42-
"symfony/var-dumper": "^6.4.8 || ^7.3.0"
41+
"phpunit/phpunit": "^10.5.16 || ^11.5.22 || ^12.5.4",
42+
"symfony/var-dumper": "^6.4.8 || ^7.4.0"
4343
},
4444
"autoload": {
4545
"psr-4": {

docs/9.0/writer/helpers.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,89 @@ $writer->insertOne(["foo", "bar"]); //will trigger a CannotInsertRecord exceptio
3232
## Charset formatter
3333

3434
[League\Csv\CharsetConverter](/9.0/converter/charset/) will help you encode your records depending on your settings.
35+
36+
37+
## Enum Formatter
38+
39+
The `League\Csv\EnumFormatter` class allows serializing `Enum` introduced in PHP8.1 as CSV field.
40+
41+
The class is **an immutable formatter** designed to convert PHP enums (`UnitEnum`) into scalar or
42+
serializable values suitable for CSV export (or similar flat formats).
43+
44+
It can be used directly as a callable and supports multiple exclusive formatting strategies.
45+
46+
| Strategy | Description |
47+
|--------------|-----------------------------------------------------|
48+
| **Native** | Uses the backed value of a `BackedEnum` |
49+
| **JSON** | Uses `jsonSerialize()` for `JsonSerializable` enums |
50+
| **Name** | Uses the enum case name (`UnitEnum::name`) |
51+
| **Callback** | Uses a user-defined callable |
52+
53+
54+
```php
55+
use League\Csv\EnumFormatter;
56+
57+
enum Status
58+
{
59+
case Active;
60+
case Inactive;
61+
}
62+
63+
$record = [
64+
'id' => 1,
65+
'status' => Status::Active,
66+
];
67+
68+
$formatter = EnumFormatter::usingName();
69+
$result = $formatter($record);
70+
// ['id' => 1, 'status' => 'Active']
71+
```
72+
73+
The class can be used directly by the `Writer` as follows:
74+
75+
```php
76+
use League\Csv\Writer;
77+
use League\Csv\EnumFormatter;
78+
79+
enum Pure implements JsonSerializable
80+
{
81+
case Foo;
82+
case Bar;
83+
84+
public function jsonSerialize(): string
85+
{
86+
return strtolower($this->name);
87+
}
88+
}
89+
90+
$arr = ['city' => Pure::Foo, 'habitants' => 7_000_000];
91+
$doc = Writer::fromString();
92+
$doc->addFormatter(EnumFormatter::usingCallback(fn (UnitEnum $value) => 'fourty-two'));
93+
$doc->insertOne($arr);
94+
95+
$doc->toString();
96+
// returns "fourty-two,7000000 \n",
97+
```
98+
99+
### Encoding a single enum
100+
101+
You may also encode a single enum manually:
102+
103+
```php
104+
use League\Csv\EnumFormatter;
105+
106+
$value = EnumFormatter::useJson()->encode(Pure::Foo);
107+
// returns "foo"
108+
```
109+
110+
### Error Handling
111+
112+
If an enum cannot be serialized using the selected strategy, a `TypeError` is thrown:
113+
114+
```php
115+
use League\Csv\EnumFormatter;
116+
117+
$formatter = EnumFormatter::useNative();
118+
$value = $formatter->encode(Pure::Foo);
119+
// Enum `Pure` cannot be serialized for CSV.
120+
```

docs/assets/img/csv_large_logo.png

20.8 KB
Loading

docs/assets/img/site.webmanifest

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name":"",
3+
"short_name":"",
4+
"icons":[
5+
{
6+
"src":"/android-chrome-192x192.png",
7+
"sizes":"192x192",
8+
"type":"image/png"
9+
},
10+
{
11+
"src":"/android-chrome-512x512.png",
12+
"sizes":"512x512",
13+
"type":"image/png"
14+
}
15+
],
16+
"theme_color":"#ffffff",
17+
"background_color":"#ffffff",
18+
"display":"standalone"
19+
}

src/EnumFormatter.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv;
15+
16+
use BackedEnum;
17+
use Closure;
18+
use JsonSerializable;
19+
use TypeError;
20+
use UnitEnum;
21+
22+
/**
23+
* Formatting strategies (exclusive):
24+
* - Callback
25+
* - JSON (JsonSerializable)
26+
* - Native (BackedEnum)
27+
* - Name (UnitEnum::name)
28+
*/
29+
class EnumFormatter
30+
{
31+
private const NATIVE_FORMAT = 1;
32+
private const JSON_FORMAT = 2;
33+
private const CALLBACK_FORMAT = 3;
34+
private const NAME_FORMAT = 4;
35+
36+
private function __construct(
37+
private int $format = self::NATIVE_FORMAT,
38+
private ?Closure $callback = null
39+
) {
40+
}
41+
42+
public static function usingJson(): self
43+
{
44+
return new self(self::JSON_FORMAT);
45+
}
46+
47+
public static function usingNative(): self
48+
{
49+
return new self(self::NATIVE_FORMAT);
50+
}
51+
52+
public static function usingName(): self
53+
{
54+
return new self(self::NAME_FORMAT);
55+
}
56+
57+
/**
58+
* @param callable(UnitEnum): mixed $callback
59+
*/
60+
public static function usingCallback(callable $callback): self
61+
{
62+
return new self(self::CALLBACK_FORMAT, $callback instanceof Closure ? $callback : $callback(...));
63+
}
64+
65+
/**
66+
* Enable using the class as a formatter for the {@link Writer}.
67+
*
68+
* @throws TypeError if encoding is invalid
69+
*/
70+
public function __invoke(array $record): array
71+
{
72+
return array_map(fn (mixed $value) => !$value instanceof UnitEnum ? $value : $this->encode($value), $record);
73+
}
74+
75+
/**
76+
* @throws TypeError If the encoding does not work
77+
*/
78+
public function encode(UnitEnum $value): mixed
79+
{
80+
return match (true) {
81+
self::NATIVE_FORMAT === $this->format && $value instanceof BackedEnum => $value->value,
82+
self::NAME_FORMAT === $this->format => $value->name,
83+
self::JSON_FORMAT === $this->format && $value instanceof JsonSerializable => $value->jsonSerialize(),
84+
self::CALLBACK_FORMAT === $this->format && null !== $this->callback => ($this->callback)($value),
85+
default => throw new TypeError('Enum `'.$value::class.'` cannot be serialized for CSV.'),
86+
};
87+
}
88+
}

src/EnumFormatterTest.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
/**
4+
* League.Csv (https://csv.thephpleague.com)
5+
*
6+
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace League\Csv;
15+
16+
use JsonSerializable;
17+
use PHPUnit\Framework\TestCase;
18+
use TypeError;
19+
use UnitEnum;
20+
21+
use function strtolower;
22+
23+
final class EnumFormatterTest extends TestCase
24+
{
25+
public function test_it_can_convert_backed_enum(): void
26+
{
27+
$arr = ['city' => City::Brussels, 'habitants' => 7_000_000];
28+
$doc = Writer::fromString();
29+
$doc->addFormatter(EnumFormatter::usingNative());
30+
$doc->insertOne($arr);
31+
32+
self::assertSame('Brussels,7000000'."\n", $doc->toString());
33+
}
34+
35+
public function test_it_can_convert_backed_enum_using_json_serializable(): void
36+
{
37+
$arr = ['city' => City::Brussels, 'habitants' => 7_000_000];
38+
$doc = Writer::fromString();
39+
$doc->addFormatter(EnumFormatter::usingJson());
40+
$doc->insertOne($arr);
41+
42+
self::assertSame('brussels,7000000'."\n", $doc->toString());
43+
}
44+
45+
public function test_it_can_convert_backed_enum_using_callback(): void
46+
{
47+
$arr = ['city' => City::Brussels, 'habitants' => 7_000_000];
48+
$doc = Writer::fromString();
49+
$doc->addFormatter(EnumFormatter::usingCallback(fn (UnitEnum $value) => 'fourty-two'));
50+
$doc->insertOne($arr);
51+
52+
self::assertSame('fourty-two,7000000'."\n", $doc->toString());
53+
}
54+
55+
public function test_it_can_convert_backed_enum_using_name(): void
56+
{
57+
$arr = ['city' => City::KINSHASA, 'habitants' => 7_000_000];
58+
$doc = Writer::fromString();
59+
$doc->addFormatter(EnumFormatter::usingName());
60+
$doc->insertOne($arr);
61+
62+
self::assertSame('KINSHASA,7000000'."\n", $doc->toString());
63+
}
64+
65+
public function test_it_fails_to_convert_an_unit_enum(): void
66+
{
67+
$arr = ['city' => Pure::Foo, 'habitants' => 7_000_000];
68+
$doc = Writer::fromString();
69+
$doc->addFormatter(EnumFormatter::usingNative());
70+
$this->expectException(TypeError::class);
71+
$doc->insertOne($arr);
72+
}
73+
74+
public function test_it_uses_json_serializable_representation_to_convert_an_unit_enum(): void
75+
{
76+
$arr = ['city' => Pure::Foo, 'habitants' => 7_000_000];
77+
$doc = Writer::fromString();
78+
$doc->addFormatter(EnumFormatter::usingJson());
79+
$doc->insertOne($arr);
80+
81+
self::assertSame('foo,7000000'."\n", $doc->toString());
82+
}
83+
84+
public function test_it_uses_name_representation_to_convert_an_unit_enum(): void
85+
{
86+
$arr = ['city' => Pure::Foo, 'habitants' => 7_000_000];
87+
$doc = Writer::fromString();
88+
$doc->addFormatter(EnumFormatter::usingName());
89+
$doc->insertOne($arr);
90+
91+
self::assertSame('Foo,7000000'."\n", $doc->toString());
92+
}
93+
94+
public function test_it_uses_callback_representation_to_convert_an_unit_enum(): void
95+
{
96+
$arr = ['city' => Pure::Foo, 'habitants' => 7_000_000];
97+
$doc = Writer::fromString();
98+
$doc->addFormatter(EnumFormatter::usingCallback(fn (UnitEnum $value) => 'fourty-two'));
99+
$doc->insertOne($arr);
100+
101+
self::assertSame('fourty-two,7000000'."\n", $doc->toString());
102+
}
103+
}
104+
105+
enum City: string implements JsonSerializable
106+
{
107+
case Kigali = 'Kigali';
108+
case KINSHASA = 'Kinshasa';
109+
case Brussels = 'Brussels';
110+
111+
public function jsonSerialize(): string
112+
{
113+
return strtolower($this->value);
114+
}
115+
}
116+
117+
enum Pure implements JsonSerializable
118+
{
119+
case Foo;
120+
case Bar;
121+
122+
public function jsonSerialize(): string
123+
{
124+
return strtolower($this->name);
125+
}
126+
}

0 commit comments

Comments
 (0)