PocketMine-MP 5.33.2 git-919492bdcad8510eb6606439eb77e1c604f1d1ea
Loading...
Searching...
No Matches
Prototype.php
1<?php declare(strict_types=1);
2
3namespace DaveRandom\CallbackValidator;
4
5use function array_map;
6use function get_class;
7
8final class Prototype{
9 private function __construct(){
10 //NOOP
11 }
12
13 private static function parameterSatisfiedBy(\ReflectionParameter $prototype, \ReflectionParameter $given) : bool{
14 return
15 //TODO: we should probably check the name as well
16 $prototype->isPassedByReference() === $given->isPassedByReference() &&
17 //contravariance can be tested as covariance by swapping the types
18 !MatchTester::isCovariant($given->getType(), $prototype->getType());
19 }
20
21 public static function isSatisfiedBy(\Closure $prototype, \Closure $callable) : bool{
22 $prototypeReflect = new \ReflectionFunction($prototype);
23 $callableReflect = new \ReflectionFunction($callable);
24
25 if($callableReflect->getNumberOfRequiredParameters() > $prototypeReflect->getNumberOfRequiredParameters()){
26 return false;
27 }
28
29 $prototypeReturn = $prototypeReflect->getReturnType();
30 $callableReturn = $callableReflect->getReturnType();
31
32 if(
33 $callableReflect->returnsReference() !== $prototypeReflect->returnsReference() ||
34 !MatchTester::isCovariant($prototypeReturn, $callableReturn)
35 ){
36 return false;
37 }
38
39 $last = null;
40
41 $prototypeParameters = $prototypeReflect->getParameters();
42 foreach($callableReflect->getParameters() as $position => $callableParameter){
43 // Parameters that exist in the prototype must always be satisfied directly
44 if(isset($prototypeParameters[$position])){
45 $prototypeParameter = $prototypeParameters[$position];
46 if(self::parameterSatisfiedBy($prototypeParameter, $callableParameter)){
47 return false;
48 }
49
50 $last = $prototypeParameter;
51 continue;
52 }
53
54 // Candidates can accept additional args that are not in the prototype as long as they are not mandatory
55 if(!$callableParameter->isOptional() && !$callableParameter->isVariadic()){
56 return false;
57 }
58
59 // If the last arg of the prototype is variadic, any additional args the candidate accepts must satisfy it
60 if($last !== null && $last->isVariadic() && !self::parameterSatisfiedBy($last, $callableParameter)){
61 return false;
62 }
63 }
64
65 return true;
66 }
67
68 public static function print(\Closure $closure) : string{
69 $reflect = new \ReflectionFunction($closure);
70 $string = 'function ';
71
72 if($reflect->returnsReference()){
73 $string .= '& ';
74 }
75
76 $string .= '( ';
77
78 $i = $o = 0;
79 $parameters = $reflect->getParameters();
80 $l = count($parameters) - 1;
81 for(; $i < $l; $i++){
82 $parameter = $parameters[$i];
83 $parameterType = $parameter->getType();
84
85 if($parameterType !== null){
86 $string .= self::printType($parameterType) . ' ';
87 }
88
89 if($parameter->isPassedByReference()){
90 $string .= '&';
91 }
92
93 if($parameter->isVariadic()){
94 $string .= '...';
95 }
96
97 $string .= '$' . $parameter->getName();
98
99 if($o === 0 && !($parameters[$i + 1]->isOptional())){
100 $string .= ', ';
101 continue;
102 }
103
104 $string .= ' [, ';
105 $o++;
106 }
107
108 if(isset($parameters[$l])){
109 $string .= $parameters[$i] . ' ';
110 }
111
112 if($o !== 0){
113 $string .= str_repeat(']', $o) . ' ';
114 }
115
116 $string .= ')';
117
118 $returnType = $reflect->getReturnType();
119 if($returnType !== null){
120 $string .= ' : ' . self::printType($returnType);
121 }
122
123 return $string;
124 }
125
129 private static function printTypes(string $symbol, array $types) : string{
130 return implode($symbol, array_map(fn(\ReflectionType $type) => self::printType($type), $types));
131 }
132
133 private static function printType(\ReflectionType $type) : string{
134 if($type instanceof \ReflectionNamedType){
135 return $type->getName();
136 }
137 if($type instanceof \ReflectionUnionType){
138 return self::printTypes('|', $type->getTypes());
139 }
140 if($type instanceof \ReflectionIntersectionType){
141 return self::printTypes('&', $type->getTypes());
142 }
143
144 throw new \AssertionError("Unhandled reflection type " . get_class($type));
145 }
146}