Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/9.0/connections/instantiation.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Alternatively, you can use the <code>fromStream</code> method.</p>
```php
public static AbstractCsv::fromStream(SplFileObject|resource $stream): self
```

Creates a new object from a stream resource or a streaming object.

```php
Expand Down
2 changes: 1 addition & 1 deletion docs/9.0/reader/record-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Converts your CSV records into PHP objects using PHP's powerful Ref

<p class="message-notice">New in version <code>9.12.0</code></p>

If you are working with a class which implements the `TabularDataReader` interface you can now deserialize
If you are working with a class which implements the `TabularData` interface you can now deserialize
your data using the `TabularDataReader::getRecordsAsObject` method. The method will convert your document records
into objects using PHP's powerful Reflection API.

Expand Down
2 changes: 2 additions & 0 deletions phpstan-build.neon
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ parameters:
treatPhpDocTypesAsCertain: false
parallel:
processTimeout: 300.0
bootstrapFiles:
- vendor/autoload.php

1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ parameters:
treatPhpDocTypesAsCertain: false
parallel:
processTimeout: 300.0

31 changes: 31 additions & 0 deletions src/Buffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Iterator;
use League\Csv\Query\Constraint\Criteria;
use League\Csv\Query\Predicate;
use League\Csv\Schema\Inspector;
use League\Csv\Schema\Schema;
use League\Csv\Serializer\Denormalizer;
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
Expand Down Expand Up @@ -203,6 +205,35 @@ public function map(callable $callback): Iterator
return MapIterator::fromIterable($this->getRecords(), $callback);
}

/**
* @param callable(TInitial|null, array<mixed>, array-key=): TInitial $callback
* @param TInitial|null $initial
*
* @template TInitial
*
* @throws SyntaxError
*
* @return TInitial|null
*/
public function reduce(callable $callback, mixed $initial = null): mixed
{
foreach ($this->getRecords() as $offset => $record) {
$initial = $callback($initial, $record, $offset);
}

return $initial;
}

public function inferSchema(?Inspector $inspector = null, array $header = []): Schema
{
return ($inspector ?? Inspector::default())->schema($this, $header);
}

public function inferRecords(?Inspector $inspector = null, array $header = []): Iterator
{
return $this->inferSchema($inspector, $header)->parse($this);
}

/**
* @param non-negative-int $nth
*
Expand Down
12 changes: 12 additions & 0 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use Deprecated;
use Iterator;
use JsonSerializable;
use League\Csv\Schema\Inspector;
use League\Csv\Schema\Schema;
use League\Csv\Serializer\Denormalizer;
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
Expand Down Expand Up @@ -416,6 +418,16 @@ public function map(callable $callback): Iterator
return MapIterator::fromIterable($this, $callback);
}

public function inferSchema(?Inspector $inspector = null, array $header = []): Schema
{
return ($inspector ?? Inspector::default())->schema($this, $header);
}

public function inferRecords(?Inspector $inspector = null, array $header = []): Iterator
{
return $this->inferSchema($inspector, $header)->parse($this);
}

/**
* @param positive-int $recordsCount
*
Expand Down
12 changes: 12 additions & 0 deletions src/ResultSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
use Generator;
use Iterator;
use JsonSerializable;
use League\Csv\Schema\Inspector;
use League\Csv\Schema\Schema;
use League\Csv\Serializer\Denormalizer;
use League\Csv\Serializer\MappingFailed;
use League\Csv\Serializer\TypeCastingFailed;
Expand Down Expand Up @@ -206,6 +208,16 @@ public function map(callable $callback): Iterator
return MapIterator::fromIterable($this, $callback);
}

public function inferSchema(?Inspector $inspector = null, array $header = []): Schema
{
return ($inspector ?? Inspector::default())->schema($this, $header);
}

public function inferRecords(?Inspector $inspector = null, array $header = []): Iterator
{
return $this->inferSchema($inspector, $header)->parse($this);
}

/**
* @param positive-int $recordsCount
*
Expand Down
62 changes: 62 additions & 0 deletions src/Schema/BooleanField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace League\Csv\Schema;

use PHPUnit\Framework\Attributes\CoversClass;

use function filter_var;
use function in_array;
use function is_bool;
use function is_string;
use function trim;

use const FILTER_NULL_ON_FAILURE;
use const FILTER_VALIDATE_BOOLEAN;

#[CoversClass(BooleanField::class)]
final class BooleanField extends FieldEvaluator implements Field
{
public function type(): FieldType
{
return FieldType::Boolean;
}

public function name(): string
{
return FieldType::Boolean->value;
}

public function parse(mixed $value): ?bool
{
if (is_bool($value)) {
return $value;
}

if (!is_string($value) && !in_array($value, [0, 1], true)) {
return null;
}

$value = trim((string) $value);
if ('' === $value) {
return null;
}

return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}

public function metadata(): FieldMetadata
{
return new FieldMetadata();
}
}
64 changes: 64 additions & 0 deletions src/Schema/BooleanFieldTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace League\Csv\Schema;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

#[CoversClass(BooleanField::class)]
final class BooleanFieldTest extends TestCase
{
private BooleanField $field;

protected function setUp(): void
{
$this->field = new BooleanField();
}

public static function provideBooleanValues(): array
{
return [
[true, true],
[false, false],
['true', true],
['false', false],
['1', true],
['0', false],
[' true ', true],
['', null],
[' ', null],
['foo', null],
[[], null],
[123, null],
];
}

#[DataProvider('provideBooleanValues')]
public function testParse(mixed $input, ?bool $expected): void
{
$result = $this->field->parse($input);

null === $expected
? self::assertNull($result)
: self::assertSame($expected, $result);
}

public function test_metadata_contains_expected_structure(): void
{
$field = new BooleanField();

self::assertTrue($field->metadata()->isEmpty());
}
}
45 changes: 45 additions & 0 deletions src/Schema/CallbackFieldParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace League\Csv\Schema;

use Closure;

/**
* @template T
*/
final class CallbackFieldParser implements FieldParser
{
/** @var Closure(mixed): ?T */
private Closure $callback;

/**
* @param (Closure(mixed): ?T)|(callable(mixed): ?T) $callback
*/
public function __construct(Closure|callable $callback)
{
if (!$callback instanceof Closure) {
$callback = $callback(...);
}

$this->callback = $callback;
}

/**
* @returns ?T
*/
public function parse(mixed $value): mixed
{
return ($this->callback)($value);
}
}
70 changes: 70 additions & 0 deletions src/Schema/CustomField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

/**
* League.Csv (https://csv.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace League\Csv\Schema;

use Closure;
use ValueError;

use function preg_match;

/**
* @template T
*/
final class CustomField extends FieldEvaluator implements Field
{
private readonly FieldParser $fieldParser;
/** @var non-empty-string */
private readonly string $fieldTypeName;

public function __construct(
FieldParser|Closure|callable $fieldParser,
string $fieldTypeName,
float $confidenceThreshold = 0.8
) {
('' !== $fieldTypeName && 1 === preg_match('/^[a-z]+(?:_[a-z0-9]+)*$/', $fieldTypeName)) || throw new ValueError('The name "'.$fieldTypeName.'" is not a valid snake case variable name.');
$fieldParser = self::resolveFieldParser($fieldParser);
parent::__construct($confidenceThreshold);

$this->fieldParser = $fieldParser;
$this->fieldTypeName = $fieldTypeName;
}

private static function resolveFieldParser(FieldParser|Closure|callable $parser): FieldParser
{
return $parser instanceof FieldParser ? $parser : new CallbackFieldParser($parser);
}

public function type(): FieldType
{
return FieldType::Custom;
}

public function name(): string
{
return FieldType::Custom->value.'('.$this->fieldTypeName.')';
}

/**
* @return ?T
*/
public function parse(mixed $value): mixed
{
return $this->fieldParser->parse($value);
}

public function metadata(): FieldMetadata
{
return new FieldMetadata();
}
}
Loading
Loading