Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

Commit

Permalink
Merge pull request #71 from bastien-phi/support_iterable
Browse files Browse the repository at this point in the history
Support for iterable type
  • Loading branch information
brendt committed Nov 28, 2019
2 parents a572b7d + 112fbd2 commit e3df15a
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 5 deletions.
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -130,6 +130,13 @@ class PostData extends DataTransferObject
*/
public $property;

/**
* Iterator of types:
*
* @var iterator<\App\Models\Author>
*/
public $property;

/**
* Union types:
*
Expand All @@ -151,6 +158,13 @@ class PostData extends DataTransferObject
*/
public $property;

/**
* Any iterator :
*
* @var iterator
*/
public $property;

/**
* No type, which allows everything
*/
Expand Down
34 changes: 30 additions & 4 deletions src/Property.php
Expand Up @@ -87,7 +87,7 @@ protected function resolveTypeDefinition()
return;
}

preg_match('/\@var ((?:(?:[\w|\\\\])+(?:\[\])?)+)/', $docComment, $matches);
preg_match('/\@var ((?:(?:[\w|\\\\<>])+(?:\[\])?)+)/', $docComment, $matches);

if (! count($matches)) {
$this->isNullable = true;
Expand All @@ -102,6 +102,10 @@ protected function resolveTypeDefinition()
$this->types = explode('|', $varDocComment);
$this->arrayTypes = str_replace('[]', '', $this->types);

if (preg_match_all('/iterable<([^|]*)>/', $varDocComment, $matches)) {
$this->arrayTypes = array_merge($this->arrayTypes, $matches[1]);
}

$this->isNullable = strpos($varDocComment, 'null') !== false;
}

Expand Down Expand Up @@ -196,7 +200,11 @@ protected function shouldBeCastToCollection(array $values): bool
protected function assertTypeEquals(string $type, $value): bool
{
if (strpos($type, '[]') !== false) {
return $this->isValidGenericCollection($type, $value);
return $this->isValidArray($type, $value);
}

if ($type === 'iterable' || strpos($type, 'iterable<') === 0) {
return $this->isValidIterable($type, $value);
}

if ($type === 'mixed' && $value !== null) {
Expand All @@ -207,16 +215,34 @@ protected function assertTypeEquals(string $type, $value): bool
|| gettype($value) === (self::$typeMapping[$type] ?? $type);
}

protected function isValidGenericCollection(string $type, $collection): bool
protected function isValidArray(string $type, $collection): bool
{
if (! is_array($collection)) {
return false;
}

$valueType = str_replace('[]', '', $type);

return $this->isValidGenericCollection($valueType, $collection);
}

protected function isValidIterable(string $type, $collection): bool
{
if (! is_iterable($collection)) {
return false;
}

if (preg_match('/^iterable<(.*)>$/', $type, $matches)) {
return $this->isValidGenericCollection($matches[1], $collection);
}

return true;
}

protected function isValidGenericCollection(string $type, $collection): bool
{
foreach ($collection as $value) {
if (! $this->assertTypeEquals($valueType, $value)) {
if (! $this->assertTypeEquals($type, $value)) {
return false;
}
}
Expand Down
62 changes: 61 additions & 1 deletion tests/DataTransferObjectTest.php
Expand Up @@ -4,14 +4,15 @@

namespace Spatie\DataTransferObject\Tests;

use ArrayIterator;
use Spatie\DataTransferObject\DataTransferObject;
use Spatie\DataTransferObject\DataTransferObjectError;
use Spatie\DataTransferObject\Tests\TestClasses\DummyClass;
use Spatie\DataTransferObject\Tests\TestClasses\EmptyChild;
use Spatie\DataTransferObject\Tests\TestClasses\OtherClass;
use Spatie\DataTransferObject\Tests\TestClasses\NestedChild;
use Spatie\DataTransferObject\Tests\TestClasses\NestedParent;
use Spatie\DataTransferObject\Tests\TestClasses\NestedParentOfMany;
use Spatie\DataTransferObject\Tests\TestClasses\OtherClass;

class DataTransferObjectTest extends TestCase
{
Expand Down Expand Up @@ -383,4 +384,63 @@ public function empty_constructor_is_supported()

$this->assertEquals(['foo' => 'abc', 'bar' => null], $valueObject->all());
}

/** @test */
public function iterable_is_supported()
{
new class(['strings' => ['foo', 'bar']]) extends DataTransferObject {
/** @var iterable<string> */
public $strings;
};

new class(['strings' => new ArrayIterator(['foo', 'bar'])]) extends DataTransferObject {
/** @var iterable<string> */
public $strings;
};

new class(['mixeds' => ['foo', 1]]) extends DataTransferObject {
/** @var iterable */
public $mixeds;
};

new class(['mixeds' => new ArrayIterator(['foo', 1])]) extends DataTransferObject {
/** @var iterable */
public $mixeds;
};

$this->markTestSucceeded();
}

/** @test */
public function an_exception_is_thrown_for_incoherent_iterator_type()
{
$this->expectException(DataTransferObjectError::class);

new class(['strings' => ['foo', 1]]) extends DataTransferObject {
/** @var iterable<string> */
public $strings;
};
}

/** @test */
public function nested_dtos_are_automatically_cast_from_arrays_to_objects_with_iterable_syntax()
{
$data = [
'children' => [
['name' => 'Alice'],
['name' => 'Bob'],
],
];

$object = new class($data) extends DataTransferObject {
/** @var iterable<\Spatie\DataTransferObject\Tests\TestClasses\NestedChild>|iterable<string> */
public $children;
};

$this->assertTrue(is_array($object->children));
$this->assertCount(2, $object->children);
$this->assertContainsOnly(NestedChild::class, $object->children);
$this->assertEquals('Alice', $object->children[0]->name);
$this->assertEquals('Bob', $object->children[1]->name);
}
}

0 comments on commit e3df15a

Please sign in to comment.