Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace PetrKnap\Optional;
* @extends OptionalObject<Some\DataObject>
*/
class YourOptional extends OptionalObject {
use NonGenericOptional;
protected static function getInstanceOf(): string {
return Some\DataObject::class;
}
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
],
"check-implementation": [
"phpcs --colors --standard=PSR12 --exclude=Generic.Files.LineLength src tests",
"phpstan analyse --level max src --ansi --no-interaction",
"phpstan analyse --level max src phpdoc_check.php --ansi --no-interaction",
"phpstan analyse --level 5 tests --ansi --no-interaction",
"phpinsights analyse src tests --ansi --no-interaction --format=github-action | sed -e \"s#::error file=$PWD/#::notice file=#g\""
],
Expand Down
157 changes: 157 additions & 0 deletions phpdoc_check.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php

/**
* This file is checked by PhpStan as production code and confirms correctness of PhpDocs
*/

declare(strict_types=1);

use PetrKnap\Optional\Optional;
use PetrKnap\Optional\OptionalArray;
use PetrKnap\Optional\OptionalInt;
use PetrKnap\Optional\OptionalObject;
use PetrKnap\Optional\OptionalString;

$functionWithGenericInputOptions = fn (Optional $string = null, Optional $int = null) => null;
$functionWithNonGenericInputOptions = fn (OptionalString $string = null, OptionalInt $int = null) => null;
$functionWithNonGenericInputs = fn (string $string = '', int $int = 0) => null;

// ---------------------------------------------------------------------------------------------------------------------

// Call all factories
OptionalString::of('');
OptionalString::of(null); // @phpstan-ignore argument.type
OptionalString::of(false); // @phpstan-ignore argument.type
OptionalString::ofFalsable('');
OptionalString::ofFalsable(null); // @phpstan-ignore argument.type
OptionalString::ofFalsable(false);
OptionalString::ofNullable('');
OptionalString::ofNullable(null);
OptionalString::ofNullable(false); // @phpstan-ignore argument.type
OptionalString::ofSingle(['']);
OptionalString::ofSingle([null]); // @phpstan-ignore argument.type
OptionalString::ofSingle([false]); // @phpstan-ignore argument.type
OptionalString::ofSingle([]);

// Check sub-typed optional factory
OptionalObject::of(new stdClass());
OptionalObject::of(new class {
});
OptionalObject::of(''); // @phpstan-ignore argument.type, argument.templateType
OptionalObject\OptionalStdClass::of(new stdClass());
OptionalObject\OptionalStdClass::of(new class { // @phpstan-ignore argument.type
});
OptionalObject\OptionalStdClass::of(''); // @phpstan-ignore argument.type

// ---------------------------------------------------------------------------------------------------------------------

// Create generic option
$stringOption = Optional::of('');

// Call all methods with generic arguments
$stringOption->filter(static fn (string $value): bool => true);
$stringOption->filter(static fn (int $value): bool => true); // @phpstan-ignore argument.type
$stringOption->flatMap(static fn (string $value): Optional => $stringOption);
$stringOption->flatMap(static fn (int $value): Optional => $stringOption); // @phpstan-ignore argument.type
$stringOption->ifPresent(static fn (string $value): string => $value);
$stringOption->ifPresent(static fn (int $value): int => $value); // @phpstan-ignore argument.type
$stringOption->map(static fn (string $value): string => $value);
$stringOption->map(static fn (int $value): int => $value); // @phpstan-ignore argument.type
$stringOptionOrElse = $stringOption->orElse('');
$stringOptionOrElseNull = $stringOption->orElse(null);
$stringOption->orElse(0); // @phpstan-ignore argument.type
$stringOptionOrElseGet = $stringOption->orElseGet(static fn (): string => '');
$stringOption->orElseGet(static fn (): int => 0); // @phpstan-ignore argument.type

// Use generic option as input for functions
$functionWithGenericInputOptions(string: $stringOption);
$functionWithNonGenericInputOptions(string: $stringOption); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(string: $stringOption->get());
$functionWithNonGenericInputs(string: $stringOption->orElseThrow());
$functionWithNonGenericInputs(string: $stringOptionOrElse);
$functionWithNonGenericInputs(string: $stringOptionOrElseNull); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(string: $stringOptionOrElseNull ?? '');
$functionWithNonGenericInputs(string: $stringOptionOrElseGet);

// Re-map generic option & call filter on it to check new generic
$intOptionMapped = $stringOption->map(static fn (string $value): int => 0);
$intOptionMappedFiltered = $intOptionMapped->filter(static fn (int $value): bool => true);
$intOptionMapped->filter(static fn (string $value): bool => true); // @phpstan-ignore argument.type
$intOptionFlatMapped = $stringOption->flatMap(static fn (string $value): Optional => Optional::of(0));
$intOptionFlatMappedFiltered = $intOptionFlatMapped->filter(static fn (int $value): bool => true);
$intOptionFlatMapped->filter(static fn (string $value): bool => true); // @phpstan-ignore argument.type

// Use re-mapped filtered options as input for functions
$functionWithGenericInputOptions(int: $intOptionMappedFiltered);
$functionWithNonGenericInputOptions(int: $intOptionMappedFiltered); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(int: $intOptionMappedFiltered->get());
$functionWithGenericInputOptions(int: $intOptionFlatMappedFiltered);
$functionWithNonGenericInputOptions(int: $intOptionFlatMappedFiltered); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(int: $intOptionFlatMappedFiltered->get());

// ---------------------------------------------------------------------------------------------------------------------

// Create non-generic option
$stringOption = OptionalString::of('');

// Call all methods with generic arguments
$stringOption->filter(static fn (string $value): bool => true);
$stringOption->filter(static fn (int $value): bool => true); // @phpstan-ignore argument.type
$stringOption->flatMap(static fn (string $value): OptionalString => $stringOption);
$stringOption->flatMap(static fn (int $value): OptionalString => $stringOption); // @phpstan-ignore argument.type
$stringOption->ifPresent(static fn (string $value): string => $value);
$stringOption->ifPresent(static fn (int $value): int => $value); // @phpstan-ignore argument.type
$stringOption->map(static fn (string $value): string => $value);
$stringOption->map(static fn (int $value): int => $value); // @phpstan-ignore argument.type
$stringOptionOrElse = $stringOption->orElse('');
$stringOptionOrElseNull = $stringOption->orElse(null);
$stringOption->orElse(0); // @phpstan-ignore argument.type
$stringOptionOrElseGet = $stringOption->orElseGet(static fn (): string => '');
$stringOption->orElseGet(static fn (): int => 0); // @phpstan-ignore argument.type

// Use non-generic option as input for functions
$functionWithGenericInputOptions(string: $stringOption);
$functionWithNonGenericInputOptions(string: $stringOption);
$functionWithNonGenericInputs(string: $stringOption->get());
$functionWithNonGenericInputs(string: $stringOption->orElseThrow());
$functionWithNonGenericInputs(string: $stringOptionOrElse);
$functionWithNonGenericInputs(string: $stringOptionOrElseNull); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(string: $stringOptionOrElseNull ?? '');
$functionWithNonGenericInputs(string: $stringOptionOrElseGet);

// Re-map typed option & call filter on it to check new generic
$intOptionMapped = $stringOption->map(static fn (string $value): int => 0);
$intOptionMappedFiltered = $intOptionMapped->filter(static fn (int $value): bool => true);
$intOptionMapped->filter(static fn (string $value): bool => true); // @phpstan-ignore argument.type
$intOptionFlatMapped = $stringOption->flatMap(static fn (string $value): OptionalInt => OptionalInt::of(0), empty: OptionalInt::empty());
$intOptionFlatMappedFiltered = $intOptionFlatMapped->filter(static fn (int $value): bool => true);
$intOptionFlatMapped->filter(static fn (string $value): bool => true); // @phpstan-ignore argument.type

// Use re-mapped filtered options as input for functions
$functionWithGenericInputOptions(int: $intOptionMappedFiltered);
$functionWithNonGenericInputOptions(int: $intOptionMappedFiltered); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(int: $intOptionMappedFiltered->get());
$functionWithGenericInputOptions(int: $intOptionFlatMappedFiltered);
$functionWithNonGenericInputOptions(int: $intOptionFlatMappedFiltered);
$functionWithNonGenericInputs(int: $intOptionFlatMappedFiltered->get());

// ---------------------------------------------------------------------------------------------------------------------

// Create complexly generic option
/** @var array{string, int} $array */
$array = ['', 0];
$arrayOption = OptionalArray::of($array);

// Call some methods with generic arguments
$arrayOptionFiltered = $arrayOption->filter(static fn (array $value): bool => true);
$arrayOption->filter(static fn (string $value): bool => true); // @phpstan-ignore argument.type
$arrayOption->orElse(['1', 1]);
$arrayOption->orElse([1, '1']); // @phpstan-ignore argument.type

// Use filtered complexly generic option as input for function
$functionWithNonGenericInputs(string: $arrayOptionFiltered->get()[0]);
$functionWithNonGenericInputs(string: $arrayOptionFiltered->get()[1]); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(int: $arrayOptionFiltered->get()[0]); // @phpstan-ignore argument.type
$functionWithNonGenericInputs(int: $arrayOptionFiltered->get()[1]);

// ---------------------------------------------------------------------------------------------------------------------
30 changes: 21 additions & 9 deletions src/JavaSe8/Optional.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,32 @@ interface Optional
{
/**
* Returns an empty {@see self::class} instance.
*
* @return self<T>
*/
public static function empty(): static;
public static function empty(): self;

/**
* Returns an {@see self::class} with the specified present non-null value.
*
* @param T $value
* @template U of T
*
* @param U $value
*
* @return self<U>
*/
public static function of(mixed $value): static;
public static function of(mixed $value): self;

/**
* Returns an {@see self::class} describing the specified value, if non-null, otherwise returns an empty {@see self::class}.
*
* @param T|null $value
* @template U of T
*
* @param U|null $value
*
* @return self<U>
*/
public static function ofNullable(mixed $value): static;
public static function ofNullable(mixed $value): self;

/**
* Indicates whether some other object is "equal to" this {@see self::class}.
Expand All @@ -43,13 +53,15 @@ public function equals(mixed $obj): bool;
* If a value is present, and the value matches the given predicate, return an {@see self::class} describing the value, otherwise return an empty {@see self::class}.
*
* @param callable(T): bool $predicate
*
* @return self<T>
*/
public function filter(callable $predicate): static;
public function filter(callable $predicate): self;

/**
* If a value is present, apply the provided {@see self::class}-bearing mapping function to it, return that result, otherwise return an empty {@see self::class}.
*
* @template U of mixed
* @template U of mixed type of non-null value
*
* @param callable(T): self<U> $mapper
*
Expand All @@ -69,7 +81,7 @@ public function get(): mixed;
/**
* If a value is present, invoke the specified consumer with the value, otherwise do nothing.
*
* @param callable(T): void $consumer
* @param callable(T): mixed $consumer
*/
public function ifPresent(callable $consumer): void;

Expand All @@ -81,7 +93,7 @@ public function isPresent(): bool;
/**
* If a value is present, apply the provided mapping function to it, and if the result is non-null, return an {@see self::class} describing the result.
*
* @template U of mixed
* @template U of mixed type of non-null value
*
* @param callable(T): U $mapper
*
Expand Down
49 changes: 49 additions & 0 deletions src/NonGenericOptional.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace PetrKnap\Optional;

/**
* Changes return types from generic to non-generic {@see Optional}
*
* @phpstan-require-extends Optional
*/
trait NonGenericOptional
{
public static function empty(): self
{
/** @var self */
return parent::empty();
}

public static function of(mixed $value): self
{
/** @var self */
return parent::of($value);
}

public static function ofFalsable(mixed $value): self
{
/** @var self */
return parent::ofFalsable($value);
}

public static function ofNullable(mixed $value): self
{
/** @var self */
return parent::ofNullable($value);
}

public static function ofSingle(iterable $value): self
{
/** @var self */
return parent::ofSingle($value);
}

public function filter(callable $predicate): self
{
/** @var self */
return parent::filter($predicate);
}
}
Loading