Skip to content

Commit 07f7a37

Browse files
committed
Rename Serializer class to Deserializer
1 parent 3ce4864 commit 07f7a37

File tree

8 files changed

+113
-97
lines changed

8 files changed

+113
-97
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ All Notable changes to `Csv` will be documented in this file
1616
- `ResultSet::fromRecords`
1717
- `Stream::setMaxLineLen`
1818
- `Stream::getMaxLineLen`
19-
- `League\Csv\Serializer` to allow casting records to objects [#508](https://github.com/thephpleague/csv/issues/508)
19+
- `League\Csv\Deserializer` to allow casting records to objects [#508](https://github.com/thephpleague/csv/issues/508)
2020

2121
### Deprecated
2222

docs/9.0/reader/record-mapping.md

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ title: Deserializing a Tabular Data record into an object
99

1010
## Assign an array to an object
1111

12-
To work with objects instead of arrays the `Serializer` class is introduced to expose a
12+
To work with objects instead of arrays the `Deserializer` class is introduced to expose a
1313
text based deserialization mechanism for tabular data.
1414

1515
The class exposes four (4) methods to ease `array` to `object` conversion:
1616

17-
- `Serializer::deserializeAll` and `Serializer::assignAll` which convert a collection of records into a collection of instances of a specified class.
18-
- `Serializer::deserialize` and `Serializer::assign` which convert a single record into a new instance of the specified class.
17+
- `Deserializer::deserializeAll` and `Deserializer::assignAll` which convert a collection of records into a collection of instances of a specified class.
18+
- `Deserializer::deserialize` and `Deserializer::assign` which convert a single record into a new instance of the specified class.
1919

2020
```php
21-
use League\Csv\Serializer;
21+
use League\Csv\Deserializer;
2222

2323
$record = [
2424
'date' => '2023-10-30',
@@ -29,7 +29,7 @@ $record = [
2929
//a complete collection of records as shown below
3030
$collection = [$record];
3131
//we first instantiate the serializer
32-
$serializer = new Serializer(Weather::class, ['date', 'temperature', 'place']);
32+
$serializer = new Deserializer(Weather::class, ['date', 'temperature', 'place']);
3333

3434
$weather = $serializer->deserialize($record); //we convert 1 record into 1 instance
3535
foreach ($serializer->deserializeAll($collection) as $weather) {
@@ -38,9 +38,9 @@ foreach ($serializer->deserializeAll($collection) as $weather) {
3838

3939
// you can use the alternate syntactic sugar methods
4040
// if you only need the deserializing mechanism once
41-
$weather = Serializer::assign(Weather::class, $record);
41+
$weather = Deserializer::assign(Weather::class, $record);
4242

43-
foreach (Serializer::assignAll(Weather::class, $collection, ['date', 'temperature', 'place']) as $weather) {
43+
foreach (Deserializer::assignAll(Weather::class, $collection, ['date', 'temperature', 'place']) as $weather) {
4444
// each $weather entry will be an instance of the Weather class;
4545
}
4646
```
@@ -113,15 +113,15 @@ enum Place
113113
}
114114
```
115115

116-
To get instances of your object, you now can call one of the `Serializer` method as show below:
116+
To get instances of your object, you now can call one of the `Deserializer` method as show below:
117117

118118
```php
119119
use League\Csv\Reader;
120-
use League\Csv\Serializer
120+
use League\Csv\Deserializer
121121

122122
$csv = Reader::createFromString($document);
123123
$csv->setHeaderOffset(0);
124-
$serializer = new Serializer(Weather::class, $csv->header());
124+
$serializer = new Deserializer(Weather::class, $csv->header());
125125

126126
foreach ($csv as $record) {
127127
$weather = $serializer->deserialize($record);
@@ -135,7 +135,7 @@ foreach ($serializer->deserializeAll($csv) as $weather) {
135135

136136
//or
137137

138-
foreach (Serializer::assignAll(Weather::class, $csv, $csv->getHeader()) as $weather) {
138+
foreach (Deserializer::assignAll(Weather::class, $csv, $csv->getHeader()) as $weather) {
139139
// each $weather entry will be an instance of the Weather class;
140140
}
141141
```
@@ -197,31 +197,31 @@ The above rule can be translated in plain english like this:
197197
198198
### Handling the empty string
199199

200-
Out of the box the `Serializer` makes no distinction between an empty string and the `null` value.
200+
Out of the box the `Deserializer` makes no distinction between an empty string and the `null` value.
201201
You can however change this behaviour using two (2) static methods:
202202

203-
- `Serializer::allowEmptyStringAsNull`
204-
- `Serializer::disallowEmptyStringAsNull`
203+
- `Deserializer::allowEmptyStringAsNull`
204+
- `Deserializer::disallowEmptyStringAsNull`
205205

206206
When called these methods will change the class behaviour when it comes to handling empty string.
207-
`Serializer::allowEmptyStringAsNull` will trigger conversion of all empty string into the `null` value
208-
before typecasting whereas `Serializer::disallowEmptyStringAsNull` will maintain the distinction.
209-
Using these methods will affect the `Serializer` usage throughout your codebase.
207+
`Deserializer::allowEmptyStringAsNull` will trigger conversion of all empty string into the `null` value
208+
before typecasting whereas `Deserializer::disallowEmptyStringAsNull` will maintain the distinction.
209+
Using these methods will affect the `Deserializer` usage throughout your codebase.
210210

211211
```php
212-
use League\Csv\Serializer;
212+
use League\Csv\Deserializer;
213213

214214
$record = [
215215
'date' => '2023-10-30',
216216
'temperature' => '',
217217
'place' => 'Berkeley',
218218
];
219219

220-
$weather = Serializer::assign(Weather::class, $record);
220+
$weather = Deserializer::assign(Weather::class, $record);
221221
$weather->temperature; // returns null
222222

223-
Serializer::disallowEmptyStringAsNull();
224-
Serializer::assign(Weather::class, $record);
223+
Deserializer::disallowEmptyStringAsNull();
224+
Deserializer::assign(Weather::class, $record);
225225
//a TypeCastingFailed exception is thrown because we
226226
//can not convert the empty string into a temperature property
227227
//which expects `null` or a non-empty string.
@@ -330,7 +330,7 @@ use League\Csv\Serializer;
330330
private array $data;
331331
```
332332

333-
In the above example, the array has a JSON value associated with the key `data` and the `Serializer` will convert the
333+
In the above example, the array has a JSON value associated with the key `data` and the `Deserializer` will convert the
334334
JSON string into an `array` and use the `JSON_BIGINT_AS_STRING` option of the `json_decode` function.
335335

336336
If you use the array shape `list` or `csv` you can also typecast the `array` content using the
@@ -355,35 +355,37 @@ The `type` option only supports scalar type (`string`, `int`, `float` and `bool`
355355

356356
## Extending Type Casting capabilities
357357

358-
We provide two mechanisms to extends typecasting. You can register a closure via the `Serializer` class
358+
We provide two mechanisms to extends typecasting. You can register a closure via the `Deserializer` class
359359
or create a fully fledge `TypeCasting` class. Of course, the choice will depend on your use case.
360360

361361
### Registering a closure
362362

363-
You can register a closure using the `Serializer` class to convert a specific type. The type can be
363+
You can register a closure using the `Deserializer` class to convert a specific type. The type can be
364364
any built-in type or a specific class.
365365

366366
```php
367367
use App\Domain\Money;
368-
use League\Csv\Serializer;
368+
use League\Csv\Deserializer;
369369

370-
Serializer::registerType(Money::class, fn (?string $value, bool $isNullable, ?int $default = null): Money => match (true) {
371-
$isNullable && null === $value => Money::fromNaira($default ?? 20_00),
370+
$typeCasting = fn (?string $value, bool $isNullable, int $default = 20_00): Money => match (true) {
371+
$isNullable && null === $value => Money::fromNaira($default),
372372
default => Money::fromNaira(filter_var($value, FILTER_VALIDATE_INT)),
373-
});
373+
};
374+
375+
Deserializer::registerType(Money::class, $typeCasting);
374376
```
375377

376-
The Serializer will automatically call the closure for any `App\Domain\Money` conversion.
378+
The `Deserializer` will automatically call the closure for any `App\Domain\Money` conversion.
377379

378380
```php
379-
use League\Csv\Serializer;
381+
use League\Csv\Deserializer;
380382

381-
Serializer::registerType('int', fn (?string $value): int => 42);
383+
Deserializer::registerType('int', fn (?string $value): int => 42);
382384
```
383385

384386
In the following example, the closure takes precedence over the `CastToInt` class to convert
385387
to the `int` type. If you still wish to use the `CastToInt` class you are require to
386-
explicitly declare it using the `Cell` attribute `cast` argument.
388+
explicitly declare it via the `Cell` attribute `cast` argument.
387389

388390
The closure signature is the following:
389391

@@ -399,7 +401,7 @@ where:
399401

400402
To complete the feature you can use:
401403

402-
- `Serializer::unregisterType` to remove the registered closure for a specific `type`;
404+
- `Deserializer::unregisterType` to remove the registered closure for a specific `type`;
403405

404406
The two (2) methods are static.
405407

src/Serializer.php renamed to src/Deserializer.php

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
use League\Csv\Serializer\TypeCastingFailed;
3232
use ReflectionAttribute;
3333
use ReflectionClass;
34-
use ReflectionException;
3534
use ReflectionMethod;
3635
use ReflectionParameter;
3736
use ReflectionProperty;
@@ -42,10 +41,9 @@
4241
use function array_search;
4342
use function array_values;
4443
use function count;
45-
use function in_array;
4644
use function is_int;
4745

48-
final class Serializer
46+
final class Deserializer
4947
{
5048
private static bool $emptyStringAsNull = true;
5149

@@ -60,15 +58,33 @@ final class Serializer
6058
* @param array<string> $propertyNames
6159
*
6260
* @throws MappingFailed
63-
* @throws ReflectionException
6461
*/
6562
public function __construct(string $className, array $propertyNames = [])
6663
{
67-
$this->class = new ReflectionClass($className);
64+
$this->class = $this->filterClass($className);
6865
$this->properties = $this->class->getProperties();
6966
$this->propertySetters = $this->findPropertySetters($propertyNames);
7067
}
7168

69+
/**
70+
* @param class-string $className
71+
*
72+
* @throws MappingFailed
73+
*/
74+
private function filterClass(string $className): ReflectionClass
75+
{
76+
if (!class_exists($className)) {
77+
throw new MappingFailed('The class `'.$className.'` does not exist or was not found.');
78+
}
79+
80+
$class = new ReflectionClass($className);
81+
if ($class->isInternal() && $class->isFinal()) {
82+
throw new MappingFailed('The class `'.$className.'` can not be deserialize using `'.self::class.'`.');
83+
}
84+
85+
return $class;
86+
}
87+
7288
public static function allowEmptyStringAsNull(): void
7389
{
7490
self::$emptyStringAsNull = true;
@@ -94,7 +110,6 @@ public static function unregisterType(string $type): void
94110
* @param array<?string> $record
95111
*
96112
* @throws MappingFailed
97-
* @throws ReflectionException
98113
* @throws TypeCastingFailed
99114
*/
100115
public static function assign(string $className, array $record): object
@@ -107,7 +122,6 @@ public static function assign(string $className, array $record): object
107122
* @param array<string> $propertyNames
108123
*
109124
* @throws MappingFailed
110-
* @throws ReflectionException
111125
* @throws TypeCastingFailed
112126
*/
113127
public static function assignAll(string $className, iterable $records, array $propertyNames = []): Iterator
@@ -134,7 +148,6 @@ public function deserializeAll(iterable $records): Iterator
134148
}
135149

136150
/**
137-
* @throws ReflectionException
138151
* @throws TypeCastingFailed
139152
*/
140153
public function deserialize(array $record): object
@@ -186,32 +199,36 @@ private function assertObjectIsInValidState(object $object): void
186199
*/
187200
private function findPropertySetters(array $propertyNames): array
188201
{
189-
$propertySetters = [];
190-
foreach ($this->class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
202+
$autoDiscoverPropertySetters = function (array $carry, ReflectionProperty $property) use ($propertyNames): array {
191203
if ($property->isStatic()) {
192-
continue;
204+
return $carry;
193205
}
194206

195207
$attribute = $property->getAttributes(Cell::class, ReflectionAttribute::IS_INSTANCEOF);
196208
if ([] !== $attribute) {
197-
continue;
209+
return $carry;
198210
}
199211

200212
/** @var int|false $offset */
201213
$offset = array_search($property->getName(), $propertyNames, true);
202214
if (false === $offset) {
203-
continue;
215+
return $carry;
204216
}
205217

206-
$propertySetters[] = new PropertySetter($property, $offset, $this->resolveTypeCasting($property));
207-
}
218+
return [...$carry, new PropertySetter($property, $offset, $this->resolveTypeCasting($property))];
219+
};
208220

209-
$propertySetters = [...$propertySetters, ...$this->findPropertySettersByAttribute($propertyNames)];
210-
if ([] === $propertySetters) {
211-
throw new MappingFailed('No properties or method setters were found eligible on the class `'.$this->class->getName().'` to be used for type casting.');
212-
}
221+
$propertySetters = [...array_reduce(
222+
$this->class->getProperties(ReflectionProperty::IS_PUBLIC),
223+
$autoDiscoverPropertySetters,
224+
[]
225+
), ...$this->findPropertySettersByAttribute($propertyNames)];
213226

214-
return $propertySetters;
227+
return match ([]) {
228+
$propertySetters => throw new MappingFailed('No properties or method setters were found eligible on the class `'.$this->class->getName().'` to be used for type casting.'),
229+
default => $propertySetters,
230+
231+
};
215232
}
216233

217234
/**
@@ -223,13 +240,11 @@ private function findPropertySettersByAttribute(array $propertyNames): array
223240
{
224241
$addPropertySetter = function (array $carry, ReflectionProperty|ReflectionMethod $accessor) use ($propertyNames) {
225242
$propertySetter = $this->findPropertySetter($accessor, $propertyNames);
226-
if (null === $propertySetter) {
227-
return $carry;
228-
}
229-
230-
$carry[] = $propertySetter;
231243

232-
return $carry;
244+
return match (null) {
245+
$propertySetter => $carry,
246+
default => [...$carry, $propertySetter],
247+
};
233248
};
234249

235250
return array_reduce(
@@ -303,7 +318,7 @@ private function getTypeCasting(Cell $cell, ReflectionProperty|ReflectionMethod
303318
return $this->resolveTypeCasting($reflectionProperty, $cell->castArguments);
304319
}
305320

306-
if (!in_array(TypeCasting::class, class_implements($typeCaster), true)) {
321+
if (!class_exists($typeCaster) || !(new ReflectionClass($typeCaster))->implementsInterface(TypeCasting::class)) {
307322
throw new MappingFailed('The class `'.$typeCaster.'` does not implements the `'.TypeCasting::class.'` interface.');
308323
}
309324

0 commit comments

Comments
 (0)