Skip to content

Commit

Permalink
Merge pull request #11 from petrknap/typed-optionals
Browse files Browse the repository at this point in the history
Implemented `TypedOptional`
  • Loading branch information
petrknap authored May 13, 2024
2 parents 4b514db + 98bc9ea commit 831ce77
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 1 deletion.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
It is an easy way to make sure that everyone has to check if they have (not) received a `null`.

## Examples

```php
namespace PetrKnap\Optional;

Expand All @@ -28,6 +30,36 @@ if ($optionalString->equals('value')) {
}
```

### Create and use your own typed optional

```php
namespace PetrKnap\Optional;

class YourClass {}

/**
* @template-extends OptionalObject<YourClass>
*/
class YourOptional extends OptionalObject {
protected static function getObjectClassName(): string {
return YourClass::class;
}
}
TypedOptional::register(YourOptional::class); // optional recommended step

function your_strong_typed_function(YourOptional $input): YourOptional {
return YourOptional::empty();
}

/**
* @param Optional<YourClass> $input
* @return Optional<YourClass>
*/
function your_weak_typed_function(Optional $input): Optional {
return YourOptional::empty();
}
```

---

Run `composer require petrknap/optional` to install it.
Expand Down
21 changes: 21 additions & 0 deletions src/Exception/CouldNotFindTypedOptionalForValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional\Exception;

use RuntimeException;

final class CouldNotFindTypedOptionalForValue extends RuntimeException implements TypedOptionalException
{
public function __construct(
private readonly mixed $value,
) {
parent::__construct();
}

public function getValue(): mixed
{
return $this->value;
}
}
27 changes: 27 additions & 0 deletions src/Exception/CouldNotRegisterNonOptional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional\Exception;

use RuntimeException;

final class CouldNotRegisterNonOptional extends RuntimeException implements TypedOptionalException
{
/**
* @param class-string $className
*/
public function __construct(
private readonly string $className,
) {
parent::__construct();
}

/**
* @return class-string
*/
public function getClassName(): string
{
return $this->className;
}
}
9 changes: 9 additions & 0 deletions src/Exception/TypedOptionalException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional\Exception;

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

declare(strict_types=1);

namespace PetrKnap\Optional;

use InvalidArgumentException;

final class TypedOptional
{
/** @var array<class-string> must be iterated in reverse order */
private static array $typedOptionals = [
OptionalArray::class,
OptionalBool::class,
OptionalFloat::class,
OptionalInt::class,
OptionalObject::class,
OptionalObject\OptionalStdClass::class,
OptionalResource::class,
OptionalResource\OptionalStream::class,
OptionalString::class,
];

/**
* @template T of mixed
*
* @param T $value
*
* @return Optional<T>
*
* @throws Exception\CouldNotFindTypedOptionalForValue
*/
public static function of(mixed $value): Optional
{
/** @var class-string<Optional<T>> $typedOptional */
foreach (array_reverse(self::$typedOptionals) as $typedOptional) {
try {
return $typedOptional::of($value);
} catch (InvalidArgumentException) {
}
}
throw new Exception\CouldNotFindTypedOptionalForValue($value);
}

/**
* @param class-string $typedOptionalClassName
*
* @throws Exception\CouldNotRegisterNonOptional
*/
public static function register(string $typedOptionalClassName): void
{
if (!is_a($typedOptionalClassName, Optional::class, allow_string: true)) {
throw new Exception\CouldNotRegisterNonOptional($typedOptionalClassName);
}
self::$typedOptionals[] = $typedOptionalClassName;
}
}
3 changes: 2 additions & 1 deletion tests/ReadmeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ public static function getPathToMarkdownFile(): string
public static function getExpectedOutputsOfPhpExamples(): iterable
{
return [
'example' => ''
'examples' => ''
. 'value'
. 'value'
. 'value'
. 'value'
. 'value'
. 'It is `value`.'
,
'create--and-use-your-own-typed-optional' => '',
];
}
}
1 change: 1 addition & 0 deletions tests/TypedOptionalsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function testCouldBeCreated(string $optionalClassName, mixed $value): voi
self::assertInstanceOf($optionalClassName, $optionalClassName::of($value));
self::assertInstanceOf($optionalClassName, $optionalClassName::ofNullable($value));
self::assertInstanceOf($optionalClassName, $optionalClassName::ofNullable(null));
self::assertInstanceOf($optionalClassName, TypedOptional::of($value));
}

public static function dataCouldBeCreated(): array
Expand Down

0 comments on commit 831ce77

Please sign in to comment.