Skip to content

Commit

Permalink
finish tests and fix some documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
0xfrej committed Nov 15, 2024
1 parent bb87657 commit 746fc55
Show file tree
Hide file tree
Showing 6 changed files with 486 additions and 73 deletions.
26 changes: 23 additions & 3 deletions src/None.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace Frej\Optional;

use Frej\Optional\Exception\OptionNoneUnwrappedException;
use Throwable;

/**
* @template T
* @extends Option<T>
* @internal
*/
class None extends Option
{
Expand Down Expand Up @@ -93,23 +95,31 @@ public function unwrapInto(callable $callback, null|string|\Throwable $error = n
*/
public function unwrapIntoOr(callable $callback, mixed $default): void
{
$this->unwrapOr($default);
$callback($this->unwrapOr($default));
}

/**
* @inheritdoc
*/
public function filter(mixed $predicate): Option
{
return None::make();
return $this;
}

/**
* @inheritdoc
*/
public function filterInto(callable $callback, mixed $predicate): void
{
$callback($this);
}

/**
* @inheritdoc
*/
public function map(callable $transformer): Option
{
return None::make();
return $this;
}

/**
Expand All @@ -122,4 +132,14 @@ public function mapOr(callable $transformer, mixed $default): Option
}
return Some::make($default);
}

public function mapInto(callable $callback, callable $transformer): void
{
$callback($this->map($transformer));
}

public function mapIntoOr(callable $callback, callable $transformer, mixed $default): void
{
$callback($this->mapOr($transformer, $default));
}
}
37 changes: 35 additions & 2 deletions src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,24 @@ abstract public function unwrapIntoOr(callable $callback, mixed $default): void;
/**
* Filter Some(T) using a predicate
*
* Calls the provided predicate function on the contained value t if the Option is Some(t), and returns Some(t) if the function returns true; otherwise, returns None
* Calls the provided predicate function on the contained value to check if the Option is Some(T), and returns Some(T) if the function returns true; otherwise, returns None
*
* @param T|callable(T): bool $predicate predicate Provided predicate lambda. If returns true, returned value will be Some(T), otherwise None
* @param T|callable(T): bool $predicate Provided predicate lambda. If returns true, returned value will be Some(T), otherwise None
* @return Option<T>
*/
abstract public function filter(mixed $predicate): Option;

/**
* Filter Some(T) using a predicate
*
* Calls the provided predicate function on the contained value to check if the Option is Some(T), and retuns Some(T) if the function returns treu; otherwise returns None
*
* @param callable(Option<T>): void $callback
* @param T|callable(T): bool $predicate Provided predcate lambda. If returns true, returned value will be Some(T),
* otherwise None
*/
abstract public function filterInto(callable $callback, mixed $predicate): void;

/**
* Tranform Option<T> to Option<U> using provided the function
*
Expand All @@ -132,6 +143,17 @@ abstract public function filter(mixed $predicate): Option;
*/
abstract public function map(callable $transformer): Option;

/**
* Tranform Option<T> to Option<U> using provided the function
*
* Leaves None unchanged
*
* @template U
* @param callable(Option<U>): void $callback
* @param callable(T): U $transformer
*/
abstract public function mapInto(callable $callback, callable $transformer): void;

/**
* Tranforms Option<T> to Option<U> using the provided function, uses `$default` value on None
*
Expand All @@ -142,6 +164,17 @@ abstract public function map(callable $transformer): Option;
*/
abstract public function mapOr(callable $transformer, mixed $default): Option;

/**
* Tranforms Option<T> to Option<U> using the provided function, uses `$default` value on None
* and inputs the value into the provided callback
*
* @template U
* @param callable(Option<U>): void $callback
* @param callable(T): U $transformer
* @param U|callable(): U $default fallback value
*/
abstract public function mapIntoOr(callable $callback, callable $transformer, mixed $default): void;

/**
* Unwraps inner value into $dst if $self is Some.
*
Expand Down
26 changes: 26 additions & 0 deletions src/Some.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/**
* @template T
* @extends Option<T>
* @internal
*/
class Some extends Option
{
Expand Down Expand Up @@ -101,6 +102,15 @@ public function filter(mixed $predicate): Option
return None::make();
}

/**
* @inheritdoc
*/
public function filterInto(callable $callback, mixed $predicate): void
{
$callback($this->filter($predicate));
}


/**
* @inheritdoc
*/
Expand All @@ -116,4 +126,20 @@ public function mapOr(callable $transformer, mixed $default): Option
{
return self::make($transformer($this->val));
}

/**
* @inheritdoc
*/
public function mapInto(callable $callback, callable $transformer): void
{
$callback($this->map($transformer));
}

/**
* @inheritdoc
*/
public function mapIntoOr(callable $callback, callable $transformer, mixed $default): void
{
$callback($this->mapOr($transformer, $default));
}
}
180 changes: 180 additions & 0 deletions tests/NoneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace Frej\Optional\Tests;

use Frej\Optional\Exception\OptionNoneUnwrappedException;
use Frej\Optional\None;
use Frej\Optional\Some;
use PHPUnit\Framework\TestCase;
use ReflectionClass;

class NoneTest extends TestCase
{
public function testIsEmpty(): void
{
$option = None::make();

$this->assertTrue($option->isEmpty());
}

public function testIsSome(): void
{
$option = None::make();

$this->assertFalse($option->isSome());
}

public function testIsNone(): void
{
$option = None::make();

$this->assertTrue($option->isNone());
}

public function testThrowsOnUnwrap(): void
{
$option = None::make();
$this->expectException(OptionNoneUnwrappedException::class);
$option->unwrap();
}

public function testThrowsCustomError(): void
{
$option = None::make();
$this->expectException(OptionNoneUnwrappedException::class);
$this->expectExceptionMessage('my test msg');
$option->unwrap('my test msg');
}

public function testThrowsCustomException(): void
{
$option = None::make();

$exception = new class () extends \Exception {};

$this->expectException($exception::class);
$option->unwrap($exception);
}

public function testUnwrapOr(): void
{
$option = None::make();

$default = 'hallo';

$r = $option->unwrapOr($default);
$this->assertEquals($default, $r);

$r = $option->unwrapOr(static fn () => $default);
$this->assertEquals($default, $r);
}

public function testUnwrapOrNull(): void
{
$option = None::make();

$this->assertNull($option->unwrapOrNull());
}

public function testUnwrapInto(): void
{
$option = None::make();

$this->expectException(OptionNoneUnwrappedException::class);
$option->unwrapInto(fn () => $this->assertFalse(true, 'unwrapInto should not call the callback for None'));
}

public function testUnwrapIntoOr(): void
{
$option = None::make();

$option->unwrapIntoOr(function ($v) {
$this->assertEquals('a', $v);
}, 'a');
}

public function testFilter(): void
{
$option = None::make();

$this->assertInstanceOf(None::class, $option->filter('whatever'));
}

public function testFiterInto(): void
{
$option = None::make();

$option->filterInto(
callback: function ($v) {
$this->assertInstanceOf(None::class, $v);
},
predicate: 'whatever'
);
}

public function testMap(): void
{
$option = None::make();

$this->assertInstanceOf(None::class, $option->map(fn () => $this->assertFalse(true, 'map should not call the transformer for None')));
}

public function testMapOr(): void
{
$option = None::make();

$r = $option->mapOr(fn () => $this->assertFalse(true, 'mapOr should not call the transformer for None'), true);

$this->assertInstanceOf(Some::class, $r);
$this->assertEquals(true, $r->unwrap());
}

public function testMapInto(): void
{
$option = None::make();

$option->mapInto(
callback: fn ($v) => $this->assertInstanceOf(None::class, $v),
transformer: fn () => $this->assertFalse(true, 'mapInto should not call the transformer for None')
);
}

public function testMapIntoOr(): void
{
$option = None::make();

$option->mapIntoOr(
callback: function ($v) {
$this->assertInstanceOf(Some::class, $v);
$this->assertEquals(true, $v->unwrap());
},
transformer: fn ($v) => $this->assertFalse(true, 'mapIntoOr should not call the transformer for None'),
default: 'me'
);
}

public function testSingletonNoneObjectIsCreatedWhenNoneCalled(): void
{
$noneOption1 = None::make();
$noneOption2 = None::make();
$this->assertSame($noneOption1, $noneOption2);
$this->assertTrue($noneOption1->isNone());
}

public function testNonePropertyIsInstantiatedAfterConstructCall(): void
{
$reflection = new ReflectionClass(None::class);
$noneProperty = $reflection->getProperty('singleton');
$noneProperty->setAccessible(true);

// Unset none for the next test
$noneProperty->setValue(null, null);

// Check after calling None method
$noneOption = None::make();
$this->assertInstanceOf(None::class, $noneProperty->getValue($noneOption));

// Unset none for the next tests
$noneProperty->setValue(null, null);
}
}
Loading

0 comments on commit 746fc55

Please sign in to comment.