Skip to content

Commit

Permalink
Improve Serializer performance with collection
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Nov 11, 2023
1 parent 77d9ea5 commit be03a29
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 114 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ It's usage will trigger a `E_USER_DEPRECATED` call.
- The internal `Stream` object it will throw a `RuntimeException` if the rewind action fails
- if calls to `fseek` fails (returns `-1` ) a new `RuntimeException` will be thrown too.
- `Stream` can iterate and return the full line respecting `SplFielObject` flags. Previously it only returned the CSV records.
- `MapIterator::fromIterable` to instantiate a `MapIterator` object from any iterable structure.

### Removed

Expand Down
35 changes: 17 additions & 18 deletions docs/9.0/reader/record-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ foreach ($serializer->deserializeAll($collection) as $weather) {
If you are working with a class which implements the `TabularDataReader` interface you can use this functionality
directly by calling the `TabularDataReader::getObjects` method.

Here's an example using the `Reader` class:
Here's an example using the `Reader` class which implements the `TabularDataReader` interface:

```php
use League\Csv\Reader;
Expand All @@ -65,7 +65,7 @@ the mechanism may either fail or produced unexpected results.</p>
To work as intended the mechanism expects the following:

- A target class where the array will be deserialized in;
- information on how to convert cell value into object properties using dedicated attributes;
- information on how to convert cell values into object properties;

As an example if we assume we have the following CSV document:

Expand All @@ -79,7 +79,7 @@ date,temperature,place
2011-01-03,5,Berkeley
```

We can define a PHP DTO using the following class and the attributes.
We can define a PHP DTO using the following properties.

```php
<?php
Expand Down Expand Up @@ -117,34 +117,33 @@ foreach ($serializer->deserializeAll($csv) as $weather) {
}
```

<p class="notice">The code above is similar to using <code>TabularDataReader::getObject</code> method.</p>

## Defining the mapping rules

By default, the deserialization engine will convert public properties using their name. In other words,
if there is a class property, which name is the same as a column name, the column value will be assigned
to this property. The appropriate type used for the record cell value is a `string` or `null` and
the object public properties ares typed with
if there is a public class property, which name is the same as a column name, the column
value will be assigned to that property. The appropriate type used for the record
cell value is a `string` or `null` and the object public properties must be typed with

- a scalar type (`string`, `int`, `float`, `bool`)
- any `Enum` object (backed or not)
- `DateTime`, `DateTimeImmuntable` or any class that extends those two classes.
- an `array`

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

To fine tune the conversion you are require to use the `Cell` attribute. This attribute will
It is possible to improve the conversion using the `Cell` attribute. This attribute will
override the automatic resolution and enable fine-tuning type casting on the property level.

The `Cell` attribute can be used on class properties and methods regardless of their visibility.
The attribute can take up to three (3) arguments which are all optional:

- The `offset` argument tells the engine which cell to use via its numeric or name offset. If not present
the property name or the name of the first argument of the `setter` method will be used. In such case,
you are required to specify the property names information.
- The `cast` argument which accept the name of a class implementing the `TypeCasting` interface and responsible
for type casting the cell value.
- The `castArguments` which enable controlling typecasting by providing extra arguments to the `TypeCasting` class constructor
- The `offset` argument tells the engine which cell to use via its numeric or name offset. If not present the property name or the name of the first argument of the `setter` method will be used. In such case, you are required to specify the property names information.
- The `cast` argument which accept the name of a class implementing the `TypeCasting` interface and responsible for type casting the cell value.
- The `castArguments` argument enables controlling typecasting by providing extra arguments to the `TypeCasting` class constructor

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

Here's an example of how the attribute could be used:

Expand All @@ -169,9 +168,9 @@ The above rule can be translated in plain english like this:
> using the date format `!Y-m-d` and the `Africa/Nairobi` timezone. Once created,
> inject the date instance into the `observedOn` property of the class.
## Type casting the record value
## Type casting

The library comes bundles with seven (7) type casting classes which relies on the property type information. All the
The library comes bundled with seven (7) type casting classes which relies on the property type information. All the
built-in methods support the `nullable` and the `mixed` types.

- They will return `null` or a specified default value, if the cell value is `null` and the type is `nullable`
Expand All @@ -198,7 +197,7 @@ optional argument `default` which is the default `int` or `float` value to retur

### CastToEnum

Convert the array value to a PHP `Enum` it supports both "real" and backed enumeration. The class takes on
Convert the array value to a PHP `Enum`, it supports both "real" and backed enumeration. The class takes on
optional argument `default` which is the default Enum value to return if the value is `null`.
If the `Enum` is backed the cell value will be considered as one of the Enum value; otherwise it will be used
as one the `Enum` name. Likewise, the `default` value will also be considered the same way. If the default value
Expand Down
9 changes: 9 additions & 0 deletions src/MapIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace League\Csv;

use ArrayIterator;
use IteratorIterator;
use Traversable;

Expand All @@ -32,6 +33,14 @@ public function __construct(Traversable $iterator, callable $callable)
$this->callable = $callable;
}

public static function fromIterable(iterable $iterator, callable $callable): self
{
return match (true) {
is_array($iterator) => new self(new ArrayIterator($iterator), $callable),
default => new self($iterator, $callable),
};
}

public function current(): mixed
{
return ($this->callable)(parent::current(), parent::key());
Expand Down
11 changes: 6 additions & 5 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function getHeaderOffset(): ?int
}

/**
* @throws Exception
* @throws SyntaxError
*
* Returns the header record.
*/
Expand All @@ -96,7 +96,7 @@ public function getHeader(): array
/**
* Determines the CSV record header.
*
* @throws Exception If the header offset is set and no record is found or is the empty array
* @throws SyntaxError If the header offset is set and no record is found or is the empty array
*
* @return array<string>
*/
Expand Down Expand Up @@ -424,11 +424,12 @@ public function getRecords(array $header = []): Iterator

/**
* @param class-string $className
* @param array<string> $header
*
* @throws TypeCastingFailed
* @throws MappingFailed
* @throws Exception
* @throws MappingFailed
* @throws ReflectionException
* @throws TypeCastingFailed
*/
public function getObjects(string $className, array $header = []): Iterator
{
Expand All @@ -448,7 +449,7 @@ public function getObjects(string $className, array $header = []): Iterator
*
* @param array<array-key, string|int> $header
*
* @throws Exception If the header contains non unique column name
* @throws SyntaxError If the header contains non unique column name
*
* @return array<int|string>
*/
Expand Down
31 changes: 20 additions & 11 deletions src/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
use LimitIterator;
use ReflectionException;

use function array_filter;
use function array_flip;
Expand Down Expand Up @@ -235,25 +236,34 @@ public function matchingFirstOrFail(string $expression): TabularDataReader
*/
public function getRecords(array $header = []): Iterator
{
if ($header !== array_filter($header, is_string(...))) {
throw SyntaxError::dueToInvalidHeaderColumnNames();
}

$header = $this->validateHeader($header);
if ([] === $header) {
$header = $this->header;
}
$header = $this->prepareHeader($header);

return $this->combineHeader($header);
}

/**
* @param class-string $className
* @param array<string> $header
*
* @throws TypeCastingFailed
* @throws Exception
* @throws MappingFailed
* @throws ReflectionException
* @throws TypeCastingFailed
*/
public function getObjects(string $className, array $header = []): Iterator
{
$header = $this->prepareHeader($header);

return (new Serializer($className, $header))->deserializeAll($this->combineHeader($header));
}

/**
* @param array<string> $header
*
* @throws SyntaxError
* @return array<string>
*/
protected function prepareHeader(array $header): array
{
if ($header !== array_filter($header, is_string(...))) {
throw SyntaxError::dueToInvalidHeaderColumnNames();
Expand All @@ -263,8 +273,7 @@ public function getObjects(string $className, array $header = []): Iterator
if ([] === $header) {
$header = $this->header;
}

return (new Serializer($className, $header))->deserializeAll($this->combineHeader($header));
return $header;
}

/**
Expand Down
Loading

0 comments on commit be03a29

Please sign in to comment.