Skip to content

Commit

Permalink
feat: implemented simple version of Optional
Browse files Browse the repository at this point in the history
  • Loading branch information
petrknap committed May 10, 2024
1 parent 0ba1ceb commit 1ce306e
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 5 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,20 @@
# php-optional
# Optional (as in Java Platform SE 8 but in PHP)

> A container object which may or may not contain a non-null value. If a value is present, `isPresent()` will return `true` and `get()` will return the value.
>
> --
> [Optional (Java Platform SE 8)](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
It is an easy way to make sure that everyone has to check if they have (not) received a `null`.

```php
namespace PetrKnap\Optional;

/** @var Optinal<string> $optionalString */
$optionalString = new Optional('value');

echo $optionalString->isPresent() ? $optionalString->get() : 'EMPTY';
```

---

Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"allow-plugins": false,
"sort-packages": true
},
"description": "php-optional",
"description": "Optional (as in Java Platform SE 8 but in PHP)",
"funding": [
{
"type": "other",
Expand All @@ -32,13 +32,13 @@
},
"require-dev": {
"nunomaduro/phpinsights": "^2.11",
"petrknap/shorts": "^2.1",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.5",
"squizlabs/php_codesniffer": "^3.7"
},
"scripts": {
"test": [
"echo TODO"
],
"test": "phpunit --colors=always --testdox tests",
"check-requirements": [
"composer outdated \"petrknap/*\" --major-only --strict --ansi --no-interaction"
],
Expand Down
11 changes: 11 additions & 0 deletions src/Exception/NoSuchElement.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional\Exception;

use RuntimeException;

final class NoSuchElement extends RuntimeException implements OptionalException
{
}
11 changes: 11 additions & 0 deletions src/Exception/OptionalException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional\Exception;

use Throwable;

interface OptionalException extends Throwable
{
}
53 changes: 53 additions & 0 deletions src/Optional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional;

use LogicException;

/**
* @template T of mixed type of non-null value
*
* @see https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
*/
final class Optional
{
private bool|null $wasPresent = null;

/**
* @param T|null $value
*/
public function __construct(
private readonly mixed $value,
) {
}

/**
* @return self<null>
*/
public static function empty(): self
{
return new self(null);
}

public function isPresent(): bool
{
return $this->wasPresent = $this->value !== null;
}

/**
* @return T
*
* @throws Exception\NoSuchElement
*/
public function get(): mixed
{
/** @var T */
return match ($this->wasPresent) {
true => $this->value,
false => throw new Exception\NoSuchElement(),
null => throw new LogicException('Call `isPresent()` before accessing the value.'),
};
}
}
38 changes: 38 additions & 0 deletions tests/OptionalTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional;

use LogicException;
use PHPUnit\Framework\TestCase;

class OptionalTest extends TestCase
{
private const VALUE = 'test';

public function testGetReturnsValueWhenValueIsPresent(): void
{
$optional = new Optional(self::VALUE);

self::assertTrue($optional->isPresent());
self::assertSame(self::VALUE, $optional->get());
}

public function testGetThrowsWhenValueIsNotPresent(): void
{
$optional = Optional::empty();

self::assertFalse($optional->isPresent());
self::expectException(Exception\NoSuchElement::class);
$optional->get();
}

public function testGetThrowsWhenCalledSeparately(): void
{
$optional = new Optional(self::VALUE);

self::expectException(LogicException::class);
$optional->get();
}
}
26 changes: 26 additions & 0 deletions tests/ReadmeTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional;

use PetrKnap\Shorts\PhpUnit\MarkdownFileTestInterface;
use PetrKnap\Shorts\PhpUnit\MarkdownFileTestTrait;
use PHPUnit\Framework\TestCase;

class ReadmeTest extends TestCase implements MarkdownFileTestInterface
{
use MarkdownFileTestTrait;

public static function getPathToMarkdownFile(): string
{
return __DIR__ . '/../README.md';
}

public static function getExpectedOutputsOfPhpExamples(): iterable
{
return [
'isPresent() -> get()' => 'value',
];
}
}

0 comments on commit 1ce306e

Please sign in to comment.