Skip to content

Commit 3d140b7

Browse files
committed
wip: temp changes
1 parent 907b3a4 commit 3d140b7

File tree

7 files changed

+275
-94
lines changed

7 files changed

+275
-94
lines changed

phpcs.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
<rule ref="Generic.Files.EndFileNewline" />
2020
<rule ref="Generic.Files.InlineHTML" />
2121
<rule ref="Generic.Files.LineEndings" />
22-
<rule ref="Generic.Files.LineLength" />
2322
<rule ref="Generic.Files.OneClassPerFile" />
2423
<rule ref="Generic.Files.OneInterfacePerFile" />
2524
<rule ref="Generic.Files.OneObjectStructurePerFile" />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
namespace Gt\Promise\Chain;
3+
4+
use Gt\Promise\PromiseException;
5+
6+
class ChainFunctionTypeError extends PromiseException {}

src/Chain/Chainable.php

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
<?php
22
namespace Gt\Promise\Chain;
33

4+
use Closure;
45
use ReflectionFunction;
6+
use ReflectionIntersectionType;
7+
use ReflectionNamedType;
8+
use ReflectionUnionType;
59
use Throwable;
610
use TypeError;
711

@@ -37,20 +41,91 @@ public function callOnRejected(Throwable $reason) {
3741
}
3842

3943
return call_user_func($this->onRejected, $reason);
40-
// try {
41-
// }
42-
// catch(TypeError $error) {
43-
// $reflection = new ReflectionFunction($this->onRejected);
44-
// $param = $reflection->getParameters()[0] ?? null;
45-
// if($param) {
46-
// $paramType = (string)$param->getType();
47-
//
48-
// if(!str_contains($error->getMessage(), "must be of type $paramType")) {
49-
// throw $error;
50-
// }
51-
// }
52-
//
53-
// return $reason;
54-
// }
44+
}
45+
46+
public function checkResolutionCallbackType(mixed $resolvedValue):void {
47+
if(isset($this->onResolved)) {
48+
$this->checkType($resolvedValue, $this->onResolved);
49+
}
50+
}
51+
52+
public function checkRejectionCallbackType(Throwable $rejection):void {
53+
if(isset($this->onRejected)) {
54+
$this->checkType($rejection, $this->onRejected);
55+
}
56+
}
57+
58+
/**
59+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
60+
* @SuppressWarnings(PHPMD.NPathComplexity)
61+
*/
62+
// phpcs:ignore
63+
private function checkType(mixed $value, callable $callable):void {
64+
if(!$callable instanceof Closure) {
65+
return;
66+
}
67+
68+
$refFunction = new ReflectionFunction($callable);
69+
$refParameterList = $refFunction->getParameters();
70+
if(!isset($refParameterList[0])) {
71+
return;
72+
}
73+
$refParameter = $refParameterList[0];
74+
$nullable = $refParameter->allowsNull();
75+
76+
if(is_null($value)) {
77+
if(!$nullable) {
78+
throw new ChainFunctionTypeError("Then function's parameter is not nullable");
79+
}
80+
}
81+
82+
$allowedTypes = [];
83+
$refType = $refParameter->getType();
84+
85+
if($refType instanceof ReflectionUnionType || $refType instanceof ReflectionIntersectionType) {
86+
foreach($refType->getTypes() as $refSubType) {
87+
array_push($allowedTypes, $refSubType->getName());
88+
}
89+
}
90+
else {
91+
/** @var ?ReflectionNamedType $refType */
92+
array_push($allowedTypes, $refType?->getName());
93+
}
94+
95+
$valueType = is_object($value)
96+
? get_class($value)
97+
: gettype($value);
98+
foreach($allowedTypes as $allowedType) {
99+
$allowedType = match($allowedType) {
100+
"int" => "integer",
101+
"float" => "double",
102+
default => $allowedType,
103+
};
104+
if(is_null($allowedType) || $allowedType === "mixed") {
105+
// A typeless property is defined - allow anything!
106+
return;
107+
}
108+
if($allowedType === $valueType) {
109+
return;
110+
}
111+
112+
if(is_a($allowedType, $valueType)) {
113+
return;
114+
}
115+
116+
117+
if($allowedType === "string") {
118+
if($valueType === "double" || $valueType === "integer") {
119+
return;
120+
}
121+
}
122+
if($allowedType === "double") {
123+
if(is_numeric($value)) {
124+
return;
125+
}
126+
}
127+
}
128+
129+
throw new ChainFunctionTypeError("Value $value is not compatible with chainable parameter");
55130
}
56131
}

src/Chain/ThenChain.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<?php
22
namespace Gt\Promise\Chain;
33

4-
class ThenChain extends Chainable {}
4+
class ThenChain extends Chainable {
5+
}

src/Promise.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33

44
use Gt\Promise\Chain\CatchChain;
55
use Gt\Promise\Chain\Chainable;
6+
use Gt\Promise\Chain\ChainFunctionTypeError;
67
use Gt\Promise\Chain\FinallyChain;
78
use Gt\Promise\Chain\ThenChain;
89
use Throwable;
910

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

1417
/** @var Chainable[] */
@@ -33,7 +36,7 @@ public function getState():PromiseState {
3336
if(isset($this->rejectedReason)) {
3437
return PromiseState::REJECTED;
3538
}
36-
elseif(isset($this->resolvedValue)) {
39+
elseif($this->resolvedValueSet) {
3740
return PromiseState::RESOLVED;
3841
}
3942

@@ -107,6 +110,7 @@ private function resolve(mixed $value):void {
107110
}
108111

109112
$this->resolvedValue = $value;
113+
$this->resolvedValueSet = true;
110114
}
111115

112116
private function reject(Throwable $reason):void {
@@ -133,6 +137,8 @@ private function tryComplete():void {
133137
}
134138
}
135139

140+
/** @SuppressWarnings(PHPMD.CyclomaticComplexity) */
141+
// phpcs:ignore
136142
private function complete():void {
137143
usort(
138144
$this->chain,
@@ -155,11 +161,28 @@ function(Chainable $a, Chainable $b) {
155161
}
156162

157163
if($chainItem instanceof ThenChain) {
164+
try {
165+
if($this->resolvedValueSet) {
166+
$chainItem->checkResolutionCallbackType($this->resolvedValue);
167+
}
168+
}
169+
catch(ChainFunctionTypeError) {
170+
continue;
171+
}
172+
158173
$this->handleThen($chainItem);
159174
}
160175
elseif($chainItem instanceof CatchChain) {
161-
if($handled = $this->handleCatch($chainItem)) {
162-
array_push($this->handledRejections, $handled);
176+
try {
177+
if(isset($this->rejectedReason)) {
178+
$chainItem->checkRejectionCallbackType($this->rejectedReason);
179+
}
180+
if($handled = $this->handleCatch($chainItem)) {
181+
array_push($this->handledRejections, $handled);
182+
}
183+
}
184+
catch(ChainFunctionTypeError) {
185+
continue;
163186
}
164187
}
165188
elseif($chainItem instanceof FinallyChain) {
@@ -180,8 +203,7 @@ private function handleThen(ThenChain $then):void {
180203
}
181204

182205
try {
183-
$result = $then->callOnResolved($this->resolvedValue)
184-
?? $this->resolvedValue ?? null;
206+
$result = $then->callOnResolved($this->resolvedValue);
185207

186208
if($result instanceof PromiseInterface) {
187209
$this->chainPromise($result);

0 commit comments

Comments
 (0)