Skip to content

Commit

Permalink
test: pass existing tests for #70 functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
g105b committed Mar 7, 2024
1 parent 907b3a4 commit c1a49be
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 95 deletions.
1 change: 0 additions & 1 deletion phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
<rule ref="Generic.Files.EndFileNewline" />
<rule ref="Generic.Files.InlineHTML" />
<rule ref="Generic.Files.LineEndings" />
<rule ref="Generic.Files.LineLength" />
<rule ref="Generic.Files.OneClassPerFile" />
<rule ref="Generic.Files.OneInterfacePerFile" />
<rule ref="Generic.Files.OneObjectStructurePerFile" />
Expand Down
6 changes: 6 additions & 0 deletions src/Chain/ChainFunctionTypeError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?php
namespace Gt\Promise\Chain;

use Gt\Promise\PromiseException;

class ChainFunctionTypeError extends PromiseException {}
104 changes: 89 additions & 15 deletions src/Chain/Chainable.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
<?php
namespace Gt\Promise\Chain;

use Closure;
use ReflectionFunction;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionUnionType;
use Throwable;
use TypeError;

Expand Down Expand Up @@ -37,20 +41,90 @@ public function callOnRejected(Throwable $reason) {
}

return call_user_func($this->onRejected, $reason);
// try {
// }
// catch(TypeError $error) {
// $reflection = new ReflectionFunction($this->onRejected);
// $param = $reflection->getParameters()[0] ?? null;
// if($param) {
// $paramType = (string)$param->getType();
//
// if(!str_contains($error->getMessage(), "must be of type $paramType")) {
// throw $error;
// }
// }
//
// return $reason;
// }
}

public function checkResolutionCallbackType(mixed $resolvedValue):void {
if(isset($this->onResolved)) {
$this->checkType($resolvedValue, $this->onResolved);
}
}

public function checkRejectionCallbackType(Throwable $rejection):void {
if(isset($this->onRejected)) {
$this->checkType($rejection, $this->onRejected);
}
}

/**
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
// phpcs:ignore
private function checkType(mixed $value, callable $callable):void {
if(!$callable instanceof Closure) {
return;
}

$refFunction = new ReflectionFunction($callable);
$refParameterList = $refFunction->getParameters();
if(!isset($refParameterList[0])) {
return;
}
$refParameter = $refParameterList[0];
$nullable = $refParameter->allowsNull();

if(is_null($value)) {
if(!$nullable) {
throw new ChainFunctionTypeError("Then function's parameter is not nullable");
}
}

$allowedTypes = [];
$refType = $refParameter->getType();

if($refType instanceof ReflectionUnionType || $refType instanceof ReflectionIntersectionType) {
foreach($refType->getTypes() as $refSubType) {
array_push($allowedTypes, $refSubType->getName());
}
}
else {
/** @var ?ReflectionNamedType $refType */
array_push($allowedTypes, $refType?->getName());
}

$valueType = is_object($value)
? get_class($value)
: gettype($value);
foreach($allowedTypes as $allowedType) {
$allowedType = match($allowedType) {
"int" => "integer",
"float" => "double",
default => $allowedType,
};
if(is_null($allowedType) || $allowedType === "mixed") {
// A typeless property is defined - allow anything!
return;
}
if($allowedType === $valueType) {
return;
}

if(is_a($valueType, $allowedType, true)) {
return;
}

if($allowedType === "string") {
if($valueType === "double" || $valueType === "integer") {
return;
}
}
if($allowedType === "double") {
if(is_numeric($value)) {
return;
}
}
}

throw new ChainFunctionTypeError("Value $value is not compatible with chainable parameter");
}
}
3 changes: 2 additions & 1 deletion src/Chain/ThenChain.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php
namespace Gt\Promise\Chain;

class ThenChain extends Chainable {}
class ThenChain extends Chainable {
}
39 changes: 33 additions & 6 deletions src/Promise.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@

use Gt\Promise\Chain\CatchChain;
use Gt\Promise\Chain\Chainable;
use Gt\Promise\Chain\ChainFunctionTypeError;
use Gt\Promise\Chain\FinallyChain;
use Gt\Promise\Chain\ThenChain;
use Throwable;

class Promise implements PromiseInterface {
private mixed $resolvedValue;
/** @var bool This is required due to the ability to set `null` as a resolved value. */
private bool $resolvedValueSet = false;
private Throwable $rejectedReason;

/** @var Chainable[] */
Expand All @@ -33,7 +36,7 @@ public function getState():PromiseState {
if(isset($this->rejectedReason)) {
return PromiseState::REJECTED;
}
elseif(isset($this->resolvedValue)) {
elseif($this->resolvedValueSet) {
return PromiseState::RESOLVED;
}

Expand Down Expand Up @@ -87,7 +90,12 @@ private function callExecutor():void {
call_user_func(
$this->executor,
function(mixed $value = null) {
$this->resolve($value);
try {
$this->resolve($value);
}
catch(PromiseException $exception) {
$this->reject($exception);
}
},
function(Throwable $reason) {
$this->reject($reason);
Expand All @@ -107,6 +115,7 @@ private function resolve(mixed $value):void {
}

$this->resolvedValue = $value;
$this->resolvedValueSet = true;
}

private function reject(Throwable $reason):void {
Expand All @@ -133,6 +142,8 @@ private function tryComplete():void {
}
}

/** @SuppressWarnings(PHPMD.CyclomaticComplexity) */
// phpcs:ignore
private function complete():void {
usort(
$this->chain,
Expand All @@ -155,11 +166,28 @@ function(Chainable $a, Chainable $b) {
}

if($chainItem instanceof ThenChain) {
try {
if($this->resolvedValueSet) {
$chainItem->checkResolutionCallbackType($this->resolvedValue);
}
}
catch(ChainFunctionTypeError) {
continue;
}

$this->handleThen($chainItem);
}
elseif($chainItem instanceof CatchChain) {
if($handled = $this->handleCatch($chainItem)) {
array_push($this->handledRejections, $handled);
try {
if(isset($this->rejectedReason)) {
$chainItem->checkRejectionCallbackType($this->rejectedReason);
}
if($handled = $this->handleCatch($chainItem)) {
array_push($this->handledRejections, $handled);
}
}
catch(ChainFunctionTypeError) {
continue;
}
}
elseif($chainItem instanceof FinallyChain) {
Expand All @@ -180,8 +208,7 @@ private function handleThen(ThenChain $then):void {
}

try {
$result = $then->callOnResolved($this->resolvedValue)
?? $this->resolvedValue ?? null;
$result = $then->callOnResolved($this->resolvedValue);

if($result instanceof PromiseInterface) {
$this->chainPromise($result);
Expand Down
Loading

0 comments on commit c1a49be

Please sign in to comment.