diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a447599..e03daca3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,7 +16,7 @@ All Notable changes to `Csv` will be documented in this file
- `ResultSet::fromRecords`
- `Stream::setMaxLineLen`
- `Stream::getMaxLineLen`
-- `League\Csv\Serializer` to allow casting records to objects [#508](https://github.com/thephpleague/csv/issues/508)
+- `League\Csv\Denormalizer` to allow casting records to objects [#508](https://github.com/thephpleague/csv/issues/508)
### Deprecated
diff --git a/docs/9.0/reader/record-mapping.md b/docs/9.0/reader/record-mapping.md
index 2e8dcb83..73cef8d0 100644
--- a/docs/9.0/reader/record-mapping.md
+++ b/docs/9.0/reader/record-mapping.md
@@ -1,6 +1,6 @@
---
layout: default
-title: Deserializing a Tabular Data record into an object
+title: Denormalize a Tabular Data record into an object
---
# Record to object conversion
@@ -9,16 +9,16 @@ title: Deserializing a Tabular Data record into an object
## Assign an array to an object
-To work with objects instead of arrays the `Serializer` class is introduced to expose a
-text based deserialization mechanism for tabular data.
+To work with objects instead of arrays the `Denormalizer` class is introduced to expose a
+text based denormalization mechanism for tabular data.
The class exposes four (4) methods to ease `array` to `object` conversion:
-- `Serializer::deserializeAll` and `Serializer::assignAll` which convert a collection of records into a collection of instances of a specified class.
-- `Serializer::deserialize` and `Serializer::assign` which convert a single record into a new instance of the specified class.
+- `Denormalizer::denormalizeAll` and `Denormalizer::assignAll` which convert a collection of records into a collection of instances of a specified class.
+- `Denormalizer::denormalize` and `Denormalizer::assign` which convert a single record into a new instance of the specified class.
```php
-use League\Csv\Serializer;
+use League\Csv\Denormalizer;
$record = [
'date' => '2023-10-30',
@@ -28,19 +28,19 @@ $record = [
//a complete collection of records as shown below
$collection = [$record];
-//we first instantiate the serializer
-$serializer = new Serializer(Weather::class, ['date', 'temperature', 'place']);
+//we first instantiate the denormalizer
+$denormalizer = new Denormalizer(Weather::class, ['date', 'temperature', 'place']);
-$weather = $serializer->deserialize($record); //we convert 1 record into 1 instance
-foreach ($serializer->deserializeAll($collection) as $weather) {
+$weather = $denormalizer->denormalize($record); //we convert 1 record into 1 instance
+foreach ($denormalizer->denormalizeAll($collection) as $weather) {
// each $weather entry will be an instance of the Weather class;
}
// you can use the alternate syntactic sugar methods
-// if you only need the deserializing mechanism once
-$weather = Serializer::assign(Weather::class, $record);
+// if you only need the denormalizing mechanism once
+$weather = Denormalizer::assign(Weather::class, $record);
-foreach (Serializer::assignAll(Weather::class, $collection, ['date', 'temperature', 'place']) as $weather) {
+foreach (Denormalizer::assignAll(Weather::class, $collection, ['date', 'temperature', 'place']) as $weather) {
// each $weather entry will be an instance of the Weather class;
}
```
@@ -64,7 +64,7 @@ In the following sections we will explain the conversion and how it can be confi
## Prerequisite
-The deserialization mechanism works mainly with DTO or objects
+The denormalization mechanism works mainly with DTO or objects
without complex logic in their constructors.
The mechanism relies on PHP's Reflection
@@ -74,7 +74,7 @@ the mechanism may either fail or produced unexpected results.
To work as intended the mechanism expects the following:
-- A target class where the array will be deserialized in;
+- A target class where the array will be denormalized in;
- information on how to convert cell values into object properties;
As an example if we assume we have the following CSV document:
@@ -113,29 +113,29 @@ enum Place
}
```
-To get instances of your object, you now can call one of the `Serializer` method as show below:
+To get instances of your object, you now can call one of the `Denormalizer` method as show below:
```php
use League\Csv\Reader;
-use League\Csv\Serializer
+use League\Csv\Denormalizer
$csv = Reader::createFromString($document);
$csv->setHeaderOffset(0);
-$serializer = new Serializer(Weather::class, $csv->header());
+$denormalizer = new Denormalizer(Weather::class, $csv->header());
foreach ($csv as $record) {
- $weather = $serializer->deserialize($record);
+ $weather = $denormalizer->denormalize($record);
}
//or
-foreach ($serializer->deserializeAll($csv) as $weather) {
+foreach ($denormalizer->denormalizeAll($csv) as $weather) {
// each $weather entry will be an instance of the Weather class;
}
//or
-foreach (Serializer::assignAll(Weather::class, $csv, $csv->getHeader()) as $weather) {
+foreach (Denormalizer::assignAll(Weather::class, $csv, $csv->getHeader()) as $weather) {
// each $weather entry will be an instance of the Weather class;
}
```
@@ -144,7 +144,7 @@ foreach (Serializer::assignAll(Weather::class, $csv, $csv->getHeader()) as $weat
## Defining the mapping rules
-By default, the deserialization engine will automatically convert public properties using their name.
+By default, the denormalization engine will automatically convert public properties using their name.
In other words, if there is a public class property, which name is the same as a record key,
the record value will be assigned to that property. The record value **MUST BE** a
`string` or `null` and the object public properties **MUST BE** typed with one of
@@ -197,19 +197,19 @@ The above rule can be translated in plain english like this:
### Handling the empty string
-Out of the box the `Serializer` makes no distinction between an empty string and the `null` value.
+Out of the box the `Denormalizer` makes no distinction between an empty string and the `null` value.
You can however change this behaviour using two (2) static methods:
-- `Serializer::allowEmptyStringAsNull`
-- `Serializer::disallowEmptyStringAsNull`
+- `Denormalizer::allowEmptyStringAsNull`
+- `Denormalizer::disallowEmptyStringAsNull`
When called these methods will change the class behaviour when it comes to handling empty string.
-`Serializer::allowEmptyStringAsNull` will trigger conversion of all empty string into the `null` value
-before typecasting whereas `Serializer::disallowEmptyStringAsNull` will maintain the distinction.
-Using these methods will affect the `Serializer` usage throughout your codebase.
+`Denormalizer::allowEmptyStringAsNull` will trigger conversion of all empty string into the `null` value
+before typecasting whereas `Denormalizer::disallowEmptyStringAsNull` will maintain the distinction.
+Using these methods will affect the `Denormalizer` usage throughout your codebase.
```php
-use League\Csv\Serializer;
+use League\Csv\Denormalizer;
$record = [
'date' => '2023-10-30',
@@ -217,11 +217,11 @@ $record = [
'place' => 'Berkeley',
];
-$weather = Serializer::assign(Weather::class, $record);
+$weather = Denormalizer::assign(Weather::class, $record);
$weather->temperature; // returns null
-Serializer::disallowEmptyStringAsNull();
-Serializer::assign(Weather::class, $record);
+Denormalizer::disallowEmptyStringAsNull();
+Denormalizer::assign(Weather::class, $record);
//a TypeCastingFailed exception is thrown because we
//can not convert the empty string into a temperature property
//which expects `null` or a non-empty string.
@@ -330,7 +330,7 @@ use League\Csv\Serializer;
private array $data;
```
-In the above example, the array has a JSON value associated with the key `data` and the `Serializer` will convert the
+In the above example, the array has a JSON value associated with the key `data` and the `Denormalizer` will convert the
JSON string into an `array` and use the `JSON_BIGINT_AS_STRING` option of the `json_decode` function.
If you use the array shape `list` or `csv` you can also typecast the `array` content using the
@@ -355,35 +355,61 @@ The `type` option only supports scalar type (`string`, `int`, `float` and `bool`
## Extending Type Casting capabilities
-We provide two mechanisms to extends typecasting. You can register a closure via the `Serializer` class
+We provide two mechanisms to extend typecasting. You can register a closure via the `Denormalizer` class
or create a fully fledge `TypeCasting` class. Of course, the choice will depend on your use case.
### Registering a closure
-You can register a closure using the `Serializer` class to convert a specific type. The type can be
+You can register a closure using the `Denormalizer` class to convert a specific type. The type can be
any built-in type or a specific class.
```php
use App\Domain\Money;
-use League\Csv\Serializer;
+use League\Csv\Denormalizer;
+
+$typeCasting = function (
+ ?string $value,
+ bool $isNullable,
+ ?int $default = 20_00
+ ): ?Money {
+ if (null === $value && $isNullable) {
+ if (null !== $default) {
+ return Money::fromNaira($default);
+ }
+
+ return null;
+ }
+
+ return Money::fromNaira(filter_var($value, FILTER_VALIDATE_INT));
+}
-Serializer::registerType(Money::class, fn (?string $value, bool $isNullable, ?int $default = null): Money => match (true) {
- $isNullable && null === $value => Money::fromNaira($default ?? 20_00),
- default => Money::fromNaira(filter_var($value, FILTER_VALIDATE_INT)),
-});
+Denormalizer::registerType(Money::class, $typeCasting);
```
-The Serializer will automatically call the closure for any `App\Domain\Money` conversion.
+The `Denormalizer` will automatically call the closure for any `App\Domain\Money` conversion. You can
+also use the `Cell` attribute to further control the conversion
+
+To do so, first, specify your casting with the attribute:
```php
+use App\Domain\Money
use League\Csv\Serializer;
-Serializer::registerType('int', fn (?string $value): int => 42);
+#[Serializer\Cell(offset: 'amount', castArguments: ['default' => 20_00])]
+private ?Money $naira;
```
+No need to specify the cast
argument as the closure is registered.
+
In the following example, the closure takes precedence over the `CastToInt` class to convert
to the `int` type. If you still wish to use the `CastToInt` class you are require to
-explicitly declare it using the `Cell` attribute `cast` argument.
+explicitly declare it via the `Cell` attribute `cast` argument.
+
+```php
+use League\Csv\Denormalizer;
+
+Denormalizer::registerType('int', fn (?string $value): int => 42);
+```
The closure signature is the following:
@@ -399,7 +425,7 @@ where:
To complete the feature you can use:
-- `Serializer::unregisterType` to remove the registered closure for a specific `type`;
+- `Denormalizer::unregisterType` to remove the registered closure for a specific `type`;
The two (2) methods are static.
@@ -408,8 +434,8 @@ The two (2) methods are static.
### Implementing a TypeCasting class
If you need to support `Intersection` type, or you want to be able to fine tune the typecasting
-you can provide your own class to typecast the value according to your own rules. To do so, first,
-specify your casting with the attribute:
+you can provide your own class to typecast the value according to your own rules. Since the class
+is not registered by default you must configure its usage via the `Cell` attribute `cast` argument.
```php
use App\Domain\Money
@@ -473,10 +499,11 @@ final class CastToNaira implements TypeCasting
public function toVariable(?string $value): ?Money
{
try {
- return match (true) {
- $this->isNullable && null === $value => $this->default,
- default => Money::fromNaira(filter_var($value, FILTER_VALIDATE_INT)),
- };
+ if (null === $value && $this->isNullable) {
+ return $this->default;
+ }
+
+ return Money::fromNaira(filter_var($value, FILTER_VALIDATE_INT));
} catch (Throwable $exception) {
throw new TypeCastingFailed('Unable to cast the given data `'.$value.'` to a `'.Money::class.'`.', 0, $exception);
}
diff --git a/src/Serializer.php b/src/Denormalizer.php
similarity index 74%
rename from src/Serializer.php
rename to src/Denormalizer.php
index e6c18511..4397b9af 100644
--- a/src/Serializer.php
+++ b/src/Denormalizer.php
@@ -31,21 +31,18 @@
use League\Csv\Serializer\TypeCastingFailed;
use ReflectionAttribute;
use ReflectionClass;
-use ReflectionException;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Throwable;
use function array_key_exists;
-use function array_reduce;
use function array_search;
use function array_values;
use function count;
-use function in_array;
use function is_int;
-final class Serializer
+final class Denormalizer
{
private static bool $emptyStringAsNull = true;
@@ -60,13 +57,12 @@ final class Serializer
* @param array $propertyNames
*
* @throws MappingFailed
- * @throws ReflectionException
*/
public function __construct(string $className, array $propertyNames = [])
{
- $this->class = new ReflectionClass($className);
+ $this->class = $this->setClass($className);
$this->properties = $this->class->getProperties();
- $this->propertySetters = $this->findPropertySetters($propertyNames);
+ $this->propertySetters = $this->setPropertySetters($propertyNames);
}
public static function allowEmptyStringAsNull(): void
@@ -79,6 +75,9 @@ public static function disallowEmptyStringAsNull(): void
self::$emptyStringAsNull = false;
}
+ /**
+ * @throws MappingFailed
+ */
public static function registerType(string $type, Closure $closure): void
{
ClosureCasting::register($type, $closure);
@@ -94,12 +93,11 @@ public static function unregisterType(string $type): void
* @param array $record
*
* @throws MappingFailed
- * @throws ReflectionException
* @throws TypeCastingFailed
*/
public static function assign(string $className, array $record): object
{
- return (new self($className, array_keys($record)))->deserialize($record);
+ return (new self($className, array_keys($record)))->denormalize($record);
}
/**
@@ -107,15 +105,14 @@ public static function assign(string $className, array $record): object
* @param array $propertyNames
*
* @throws MappingFailed
- * @throws ReflectionException
* @throws TypeCastingFailed
*/
public static function assignAll(string $className, iterable $records, array $propertyNames = []): Iterator
{
- return (new self($className, $propertyNames))->deserializeAll($records);
+ return (new self($className, $propertyNames))->denormalizeAll($records);
}
- public function deserializeAll(iterable $records): Iterator
+ public function denormalizeAll(iterable $records): Iterator
{
$check = true;
$assign = function (array $record) use (&$check) {
@@ -134,10 +131,9 @@ public function deserializeAll(iterable $records): Iterator
}
/**
- * @throws ReflectionException
* @throws TypeCastingFailed
*/
- public function deserialize(array $record): object
+ public function denormalize(array $record): object
{
$object = $this->class->newInstanceWithoutConstructor();
@@ -178,65 +174,45 @@ private function assertObjectIsInValidState(object $object): void
}
/**
- * @param array $propertyNames
+ * @param class-string $className
*
* @throws MappingFailed
- *
- * @return non-empty-array
*/
- private function findPropertySetters(array $propertyNames): array
+ private function setClass(string $className): ReflectionClass
{
- $propertySetters = [];
- foreach ($this->class->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
- if ($property->isStatic()) {
- continue;
- }
-
- $attribute = $property->getAttributes(Cell::class, ReflectionAttribute::IS_INSTANCEOF);
- if ([] !== $attribute) {
- continue;
- }
-
- /** @var int|false $offset */
- $offset = array_search($property->getName(), $propertyNames, true);
- if (false === $offset) {
- continue;
- }
-
- $propertySetters[] = new PropertySetter($property, $offset, $this->resolveTypeCasting($property));
+ if (!class_exists($className)) {
+ throw new MappingFailed('The class `'.$className.'` does not exist or was not found.');
}
- $propertySetters = [...$propertySetters, ...$this->findPropertySettersByAttribute($propertyNames)];
- if ([] === $propertySetters) {
- throw new MappingFailed('No properties or method setters were found eligible on the class `'.$this->class->getName().'` to be used for type casting.');
+ $class = new ReflectionClass($className);
+ if ($class->isInternal() && $class->isFinal()) {
+ throw new MappingFailed('The class `'.$className.'` can not be deserialize using `'.self::class.'`.');
}
- return $propertySetters;
+ return $class;
}
/**
* @param array $propertyNames
*
- * @return array
+ * @throws MappingFailed
+ *
+ * @return non-empty-array
*/
- private function findPropertySettersByAttribute(array $propertyNames): array
+ private function setPropertySetters(array $propertyNames): array
{
- $addPropertySetter = function (array $carry, ReflectionProperty|ReflectionMethod $accessor) use ($propertyNames) {
+ $propertySetters = [];
+ foreach ([...$this->properties, ...$this->class->getMethods(ReflectionMethod::IS_PUBLIC)] as $accessor) {
$propertySetter = $this->findPropertySetter($accessor, $propertyNames);
- if (null === $propertySetter) {
- return $carry;
+ if (null !== $propertySetter) {
+ $propertySetters[] = $propertySetter;
}
+ }
- $carry[] = $propertySetter;
-
- return $carry;
+ return match ([]) {
+ $propertySetters => throw new MappingFailed('No property or method from `'.$this->class->getName().'` can be used for deserialization.'),
+ default => $propertySetters,
};
-
- return array_reduce(
- [...$this->properties, ...$this->class->getMethods(ReflectionMethod::IS_PUBLIC)],
- $addPropertySetter,
- []
- );
}
/**
@@ -248,7 +224,21 @@ private function findPropertySetter(ReflectionProperty|ReflectionMethod $accesso
{
$attributes = $accessor->getAttributes(Cell::class, ReflectionAttribute::IS_INSTANCEOF);
if ([] === $attributes) {
- return null;
+ if (!$accessor instanceof ReflectionProperty) {
+ return null;
+ }
+
+ if ($accessor->isStatic()) {
+ return null;
+ }
+
+ /** @var int|false $offset */
+ $offset = array_search($accessor->getName(), $propertyNames, true);
+
+ return match (false) {
+ $offset => null,
+ default => new PropertySetter($accessor, $offset, $this->resolveTypeCasting($accessor)),
+ };
}
if (1 < count($attributes)) {
@@ -277,11 +267,11 @@ private function findPropertySetter(ReflectionProperty|ReflectionMethod $accesso
/** @var int<0, max>|false $index */
$index = array_search($offset, $propertyNames, true);
- if (false === $index) {
- throw new MappingFailed('The offset `'.$offset.'` could not be found in the header; Pleaser verify your header data.');
- }
- return new PropertySetter($accessor, $index, $cast);
+ return match (false) {
+ $index => throw new MappingFailed('The offset `'.$offset.'` could not be found in the header; Pleaser verify your header data.'),
+ default => new PropertySetter($accessor, $index, $cast),
+ };
}
/**
@@ -303,8 +293,8 @@ private function getTypeCasting(Cell $cell, ReflectionProperty|ReflectionMethod
return $this->resolveTypeCasting($reflectionProperty, $cell->castArguments);
}
- if (!in_array(TypeCasting::class, class_implements($typeCaster), true)) {
- throw new MappingFailed('The class `'.$typeCaster.'` does not implements the `'.TypeCasting::class.'` interface.');
+ if (!class_exists($typeCaster) || !(new ReflectionClass($typeCaster))->implementsInterface(TypeCasting::class)) {
+ throw new MappingFailed('`'.$typeCaster.'` must be an resolvable class implementing the `'.TypeCasting::class.'` interface.');
}
try {
@@ -312,12 +302,10 @@ private function getTypeCasting(Cell $cell, ReflectionProperty|ReflectionMethod
$cast = new $typeCaster(...$cell->castArguments, ...['reflectionProperty' => $reflectionProperty]);
return $cast;
+ } catch (MappingFailed $exception) {
+ throw $exception;
} catch (Throwable $exception) {
- if ($exception instanceof MappingFailed) {
- throw $exception;
- }
-
- throw new MappingFailed(message:'Unable to instantiate a casting mechanism. Please verify your casting arguments', previous: $exception);
+ throw new MappingFailed('Unable to load the casting mechanism. Please verify your casting arguments', 0, $exception);
}
}
@@ -345,10 +333,10 @@ private function resolveTypeCasting(ReflectionProperty|ReflectionParameter $refl
Type::Enum => new CastToEnum(...$arguments),
default => throw $exception,
};
- } catch (MappingFailed $exception) {
- throw $exception;
+ } catch (MappingFailed $mappingFailed) {
+ throw $mappingFailed;
} catch (Throwable $exception) {
- throw new MappingFailed(message:'Unable to load the casting mechanism. Please verify your casting arguments', previous: $exception);
+ throw new MappingFailed('Unable to load the casting mechanism. Please verify your casting arguments', 0, $exception);
}
}
}
diff --git a/src/SerializerTest.php b/src/DenormalizerTest.php
similarity index 81%
rename from src/SerializerTest.php
rename to src/DenormalizerTest.php
index 26c7ed31..1f6cd33e 100644
--- a/src/SerializerTest.php
+++ b/src/DenormalizerTest.php
@@ -26,7 +26,7 @@
use stdClass;
use Traversable;
-final class SerializerTest extends TestCase
+final class DenormalizerTest extends TestCase
{
public function testItConvertsAnIterableListOfRecords(): void
{
@@ -43,7 +43,7 @@ public function testItConvertsAnIterableListOfRecords(): void
],
];
- $results = [...Serializer::assignAll(WeatherWithRecordAttribute::class, $records, ['date', 'temperature', 'place'])];
+ $results = [...Denormalizer::assignAll(WeatherWithRecordAttribute::class, $records, ['date', 'temperature', 'place'])];
self::assertCount(2, $results);
foreach ($results as $result) {
self::assertInstanceOf(WeatherWithRecordAttribute::class, $result);
@@ -58,7 +58,7 @@ public function testItConvertsARecordsToAnObjectUsingRecordAttribute(): void
'place' => 'Berkeley',
];
- $weather = Serializer::assign(WeatherWithRecordAttribute::class, $record);
+ $weather = Denormalizer::assign(WeatherWithRecordAttribute::class, $record);
self::assertInstanceOf(WeatherWithRecordAttribute::class, $weather);
self::assertInstanceOf(DateTimeImmutable::class, $weather->observedOn);
@@ -74,7 +74,7 @@ public function testItConvertsARecordsToAnObjectUsingProperties(): void
'place' => 'Berkeley',
];
- $weather = Serializer::assign(WeatherProperty::class, $record);
+ $weather = Denormalizer::assign(WeatherProperty::class, $record);
self::assertInstanceOf(WeatherProperty::class, $weather);
self::assertInstanceOf(DateTimeImmutable::class, $weather->observedOn);
@@ -90,7 +90,7 @@ public function testItConvertsARecordsToAnObjectUsingMethods(): void
'place' => 'Berkeley',
];
- $weather = Serializer::assign(WeatherSetterGetter::class, $record);
+ $weather = Denormalizer::assign(WeatherSetterGetter::class, $record);
self::assertInstanceOf(WeatherSetterGetter::class, $weather);
self::assertSame('2023-10-30', $weather->getObservedOn()->format('Y-m-d'));
@@ -101,9 +101,9 @@ public function testItConvertsARecordsToAnObjectUsingMethods(): void
public function testMappingFailBecauseTheRecordAttributeIsMissing(): void
{
$this->expectException(MappingFailed::class);
- $this->expectExceptionMessage('No properties or method setters were found eligible on the class `stdClass` to be used for type casting.');
+ $this->expectExceptionMessage('No property or method from `stdClass` can be used for deserialization.');
- Serializer::assign(stdClass::class, ['foo' => 'bar']);
+ Denormalizer::assign(stdClass::class, ['foo' => 'bar']);
}
public function testItWillThrowIfTheHeaderIsMissingAndTheColumnOffsetIsAString(): void
@@ -111,8 +111,8 @@ public function testItWillThrowIfTheHeaderIsMissingAndTheColumnOffsetIsAString()
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('Column name as string are only supported if the tabular data has a non-empty header.');
- $serializer = new Serializer(WeatherSetterGetter::class);
- $serializer->deserialize([
+ $serializer = new Denormalizer(WeatherSetterGetter::class);
+ $serializer->denormalize([
'date' => '2023-10-30',
'temperature' => '-1.5',
'place' => 'Berkeley',
@@ -124,8 +124,8 @@ public function testItWillThrowIfTheHeaderContainsInvalidOffsetName(): void
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('The offset `temperature` could not be found in the header; Pleaser verify your header data.');
- $serializer = new Serializer(WeatherSetterGetter::class, ['date', 'toto', 'foobar']);
- $serializer->deserialize([
+ $serializer = new Denormalizer(WeatherSetterGetter::class, ['date', 'toto', 'foobar']);
+ $serializer->denormalize([
'date' => '2023-10-30',
'temperature' => '-1.5',
'place' => 'Berkeley',
@@ -137,15 +137,15 @@ public function testItWillThrowIfTheColumnAttributesIsUsedMultipleTimeForTheSame
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('Using more than one `League\Csv\Serializer\Cell` attribute on a class property or method is not supported.');
- new Serializer(InvalidWeatherAttributeUsage::class);
+ new Denormalizer(InvalidWeatherAttributeUsage::class);
}
public function testItWillThrowIfTheColumnAttributesCasterIsInvalid(): void
{
$this->expectException(MappingFailed::class);
- $this->expectExceptionMessage('The class `stdClass` does not implements the `League\Csv\Serializer\TypeCasting` interface.');
+ $this->expectExceptionMessage('`stdClass` must be an resolvable class implementing the `League\Csv\Serializer\TypeCasting` interface.');
- new Serializer(InvalidWeatherAttributeCasterNotSupported::class);
+ new Denormalizer(InvalidWeatherAttributeCasterNotSupported::class);
}
public function testItWillThrowBecauseTheObjectDoesNotHaveTypedProperties(): void
@@ -153,7 +153,7 @@ public function testItWillThrowBecauseTheObjectDoesNotHaveTypedProperties(): voi
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('The property `temperature` must be typed with a supported type.');
- new Serializer(InvaliDWeatherWithRecordAttribute::class, ['temperature', 'foobar', 'observedOn']);
+ new Denormalizer(InvaliDWeatherWithRecordAttribute::class, ['temperature', 'foobar', 'observedOn']);
}
public function testItWillFailForLackOfTypeCasting(): void
@@ -161,7 +161,7 @@ public function testItWillFailForLackOfTypeCasting(): void
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('The property `observedOn` must be typed with a supported type.');
- new Serializer(InvaliDWeatherWithRecordAttributeAndUnknownCasting::class, ['temperature', 'place', 'observedOn']);
+ new Denormalizer(InvaliDWeatherWithRecordAttributeAndUnknownCasting::class, ['temperature', 'place', 'observedOn']);
}
public function testItWillThrowIfTheClassContainsUninitializedProperties(): void
@@ -169,7 +169,7 @@ public function testItWillThrowIfTheClassContainsUninitializedProperties(): void
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('The property `annee` must be typed with a supported type.');
- Serializer::assign(
+ Denormalizer::assign(
InvalidObjectWithUninitializedProperty::class,
['prenoms' => 'John', 'nombre' => '42', 'sexe' => 'M', 'annee' => '2018']
);
@@ -184,7 +184,7 @@ public function testItCanNotAutodiscoverWithIntersectionType(): void
public Countable&Traversable $traversable;
};
- Serializer::assign($foobar::class, ['traversable' => '1']);
+ Denormalizer::assign($foobar::class, ['traversable' => '1']);
}
public function testItCanUseTheClosureRegisteringMechanism(): void
@@ -194,13 +194,13 @@ public function testItCanUseTheClosureRegisteringMechanism(): void
public string $foo;
};
- Serializer::registerType('string', fn (?string $value) => 'yolo!');
+ Denormalizer::registerType('string', fn (?string $value) => 'yolo!');
- self::assertSame('yolo!', Serializer::assign($foobar::class, $record)->foo); /* @phpstan-ignore-line */
+ self::assertSame('yolo!', Denormalizer::assign($foobar::class, $record)->foo); /* @phpstan-ignore-line */
- Serializer::unregisterType('string');
+ Denormalizer::unregisterType('string');
- self::assertSame('toto', Serializer::assign($foobar::class, $record)->foo);
+ self::assertSame('toto', Denormalizer::assign($foobar::class, $record)->foo);
}
public function testItFailsToRegisterUnknownType(): void
@@ -209,7 +209,7 @@ public function testItFailsToRegisterUnknownType(): void
$this->expectException(MappingFailed::class);
$this->expectExceptionMessage('The `'.$type.'` could not be register.');
- Serializer::registerType($type, fn (?string $value) => 'yolo!');
+ Denormalizer::registerType($type, fn (?string $value) => 'yolo!');
}
public function testEmptyStringHandling(): void
@@ -219,13 +219,13 @@ public function testEmptyStringHandling(): void
public ?string $foo;
};
- Serializer::disallowEmptyStringAsNull();
+ Denormalizer::disallowEmptyStringAsNull();
- self::assertSame('', Serializer::assign($foobar::class, $record)->foo); /* @phpstan-ignore-line */
+ self::assertSame('', Denormalizer::assign($foobar::class, $record)->foo); /* @phpstan-ignore-line */
- Serializer::allowEmptyStringAsNull();
+ Denormalizer::allowEmptyStringAsNull();
- self::assertNull(Serializer::assign($foobar::class, $record)->foo);
+ self::assertNull(Denormalizer::assign($foobar::class, $record)->foo);
}
}
diff --git a/src/Reader.php b/src/Reader.php
index 16de14fb..638c0c8e 100644
--- a/src/Reader.php
+++ b/src/Reader.php
@@ -19,7 +19,6 @@
use JsonSerializable;
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
-use ReflectionException;
use SplFileObject;
use function array_filter;
@@ -442,7 +441,6 @@ protected function prepareHeader($header = []): array
*
* @throws Exception
* @throws MappingFailed
- * @throws ReflectionException
* @throws TypeCastingFailed
*/
public function getObjects(string $className, array $header = []): Iterator
@@ -450,7 +448,7 @@ public function getObjects(string $className, array $header = []): Iterator
/** @var array $header */
$header = $this->prepareHeader($header);
- return Serializer::assignAll(
+ return Denormalizer::assignAll(
$className,
$this->combineHeader($this->prepareRecords(), $header),
$header
diff --git a/src/ResultSet.php b/src/ResultSet.php
index 3e8a618e..f09d7f3e 100644
--- a/src/ResultSet.php
+++ b/src/ResultSet.php
@@ -22,7 +22,6 @@
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
use LimitIterator;
-use ReflectionException;
use function array_filter;
use function array_flip;
@@ -247,14 +246,13 @@ public function getRecords(array $header = []): Iterator
*
* @throws Exception
* @throws MappingFailed
- * @throws ReflectionException
* @throws TypeCastingFailed
*/
public function getObjects(string $className, array $header = []): Iterator
{
$header = $this->prepareHeader($header);
- return Serializer::assignAll(
+ return Denormalizer::assignAll(
$className,
$this->combineHeader($header),
$header
diff --git a/src/Serializer/ClosureCasting.php b/src/Serializer/ClosureCasting.php
index fe72a150..5ba0cd58 100644
--- a/src/Serializer/ClosureCasting.php
+++ b/src/Serializer/ClosureCasting.php
@@ -21,6 +21,9 @@
use ReflectionUnionType;
use Throwable;
+use function array_key_exists;
+use function class_exists;
+
final class ClosureCasting implements TypeCasting
{
/** @var array */
@@ -60,11 +63,11 @@ public function toVariable(?string $value): mixed
public static function register(string $type, Closure $closure): void
{
- if (!class_exists($type) && !(Type::tryFrom($type)?->isBuiltIn() ?? false)) {
- throw new MappingFailed('The `'.$type.'` could not be register.');
- }
-
- self::$casters[$type] = $closure;
+ self::$casters[$type] = match (true) {
+ class_exists($type),
+ Type::tryFrom($type)?->isBuiltIn() ?? false => $closure,
+ default => throw new MappingFailed('The `'.$type.'` could not be register.'),
+ };
}
public static function unregister(string $type): void
diff --git a/src/Serializer/Type.php b/src/Serializer/Type.php
index 4f5cee1f..a3a1e057 100644
--- a/src/Serializer/Type.php
+++ b/src/Serializer/Type.php
@@ -13,6 +13,7 @@
use DateTimeInterface;
+use ReflectionClass;
use ReflectionNamedType;
use ReflectionParameter;
use ReflectionProperty;
@@ -20,7 +21,6 @@
use ReflectionUnionType;
use function class_exists;
-use function class_implements;
use function enum_exists;
use function in_array;
use function interface_exists;
@@ -168,7 +168,7 @@ private static function tryFromName(string $propertyType): ?self
$type instanceof self => $type,
enum_exists($propertyType) => self::Enum,
interface_exists($propertyType) && DateTimeInterface::class === $propertyType,
- class_exists($propertyType) && in_array(DateTimeInterface::class, class_implements($propertyType), true) => self::Date,
+ class_exists($propertyType) && (new ReflectionClass($propertyType))->implementsInterface(DateTimeInterface::class) => self::Date,
default => null,
};
}