Skip to content

Commit

Permalink
add nullable types
Browse files Browse the repository at this point in the history
  • Loading branch information
Baptouuuu committed Nov 3, 2019
1 parent 5e106a3 commit a1da573
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 9 deletions.
28 changes: 28 additions & 0 deletions src/Specification/NullableType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
declare(strict_types = 1);

namespace Innmind\Immutable\Specification;

use Innmind\Immutable\SpecificationInterface;

final class NullableType implements SpecificationInterface
{
private $type;

public function __construct(SpecificationInterface $type)
{
$this->type = $type;
}

/**
* {@inheritdoc}
*/
public function validate($value): void
{
if (\is_null($value)) {
return;
}

$this->type->validate($value);
}
}
38 changes: 30 additions & 8 deletions src/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
Specification\PrimitiveType,
Specification\ClassType,
Specification\VariableType,
Specification\MixedType
Specification\MixedType,
Specification\NullableType
};

final class Type
Expand All @@ -21,19 +22,23 @@ final class Type
*/
public static function of(string $type): SpecificationInterface
{
if (\function_exists('is_'.$type)) {
return new PrimitiveType($type);
if ($type === '?null') {
throw new \ParseError('\'null\' type is already nullable');
}

if ($type === 'variable') {
return new VariableType;
if ($type === '?mixed') {
throw new \ParseError('\'mixed\' type already accepts \'null\' values');
}

if ($type === 'mixed') {
return new MixedType;
$type = Str::of($type);

if ($type->startsWith('?')) {
return new NullableType(
self::ofPrimitive((string) $type->drop(1))
);
}

return new ClassType($type);
return self::ofPrimitive((string) $type);
}

/**
Expand Down Expand Up @@ -67,4 +72,21 @@ public static function determine($value): string
return $type;
}
}

private static function ofPrimitive(string $type): SpecificationInterface
{
if (\function_exists('is_'.$type)) {
return new PrimitiveType($type);
}

if ($type === 'variable') {
return new VariableType;
}

if ($type === 'mixed') {
return new MixedType;
}

return new ClassType($type);
}
}
48 changes: 48 additions & 0 deletions tests/Specification/NullableTypeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types = 1);

namespace Tests\Innmind\Immutable\Specification;

use Innmind\Immutable\{
Specification\NullableType,
SpecificationInterface,
};
use PHPUnit\Framework\TestCase;

class NullableTypeTest extends TestCase
{
public function testInterface()
{
$type = new NullableType(
$this->createMock(SpecificationInterface::class)
);

$this->assertInstanceOf(SpecificationInterface::class, $type);
}

public function testDoesntThrowWhenValueIsNull()
{
$type = new NullableType(
$inner = $this->createMock(SpecificationInterface::class)
);
$inner
->expects($this->never())
->method('validate');

$this->assertNull($type->validate(null));
}

public function testUseUnderlyingTypeWhenValueIsNotNull()
{
$type = new NullableType(
$inner = $this->createMock(SpecificationInterface::class)
);
$value = new \stdClass;
$inner
->expects($this->once())
->method('validate')
->with($value);

$this->assertNull($type->validate($value));
}
}
36 changes: 35 additions & 1 deletion tests/TypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
Specification\PrimitiveType,
Specification\VariableType,
Specification\MixedType,
Specification\ClassType
Specification\ClassType,
Specification\NullableType
};
use PHPUnit\Framework\TestCase;

Expand All @@ -26,6 +27,39 @@ public function testGetSpecificationFor()
$this->assertInstanceOf(VariableType::class, Type::of('variable'));
$this->assertInstanceOf(MixedType::class, Type::of('mixed'));
$this->assertInstanceOf(ClassType::class, Type::of('stdClass'));
$this->assertInstanceOf(NullableType::class, Type::of('?string'));
$this->assertInstanceOf(NullableType::class, Type::of('?int'));
$this->assertInstanceOf(NullableType::class, Type::of('?float'));
$this->assertInstanceOf(NullableType::class, Type::of('?array'));
$this->assertInstanceOf(NullableType::class, Type::of('?bool'));
$this->assertInstanceOf(NullableType::class, Type::of('?object'));
$this->assertInstanceOf(NullableType::class, Type::of('?variable'));
$this->assertInstanceOf(NullableType::class, Type::of('?stdClass'));

$this->assertNull(Type::of('?string')->validate('foo'));
$this->assertNull(Type::of('?int')->validate(42));
$this->assertNull(Type::of('?float')->validate(2.4));
$this->assertNull(Type::of('?array')->validate([]));
$this->assertNull(Type::of('?bool')->validate(true));
$this->assertNull(Type::of('?object')->validate(new \stdClass));
$this->assertNull(Type::of('?variable')->validate(false));
$this->assertNull(Type::of('?stdClass')->validate(new \stdClass));
}

public function testTypeOfNullableNullIsNotAccepted()
{
$this->expectException(\ParseError::class);
$this->expectExceptionMessage('\'null\' type is already nullable');

Type::of('?null');
}

public function testTypeOfNullableMixedIsNotAccepted()
{
$this->expectException(\ParseError::class);
$this->expectExceptionMessage('\'mixed\' type already accepts \'null\' values');

Type::of('?mixed');
}

public function testDetermineType()
Expand Down

0 comments on commit a1da573

Please sign in to comment.