1 | <?php |
||
2 | /** |
||
3 | * @author SignpostMarv |
||
4 | */ |
||
5 | declare(strict_types=1); |
||
6 | |||
7 | namespace SignpostMarv\DaftMagicPropertyAnalysis; |
||
8 | |||
9 | use Closure; |
||
10 | use InvalidArgumentException; |
||
11 | |||
12 | /** |
||
13 | * @template T as object |
||
14 | */ |
||
15 | class DefinitionAssistant |
||
16 | { |
||
17 | const ARG_INDEX_CLOSURE_GETTER = 2; |
||
18 | |||
19 | const ARG_INDEX_CLOSURE_SETTER = 3; |
||
20 | |||
21 | const IN_ARRAY_STRICT_MODE = true; |
||
22 | |||
23 | const COUNT_EXPECTED_REQUIRED_PARAMETERS = 1; |
||
24 | |||
25 | const PARAM_INDEX_FIRST = 0; |
||
26 | |||
27 | const BOOL_IS_PARAM = true; |
||
28 | |||
29 | const BOOL_IS_RETURN = false; |
||
30 | |||
31 | /** |
||
32 | * @var array<string, array<int, string>> |
||
33 | * |
||
34 | * @psalm-var array<class-string<T>, array<int, string>> |
||
35 | */ |
||
36 | protected static $properties = []; |
||
37 | |||
38 | /** |
||
39 | * @var array<string, Closure> |
||
40 | * |
||
41 | * @psalm-var array<class-string<T>, Closure(string):?string> |
||
42 | */ |
||
43 | protected static $getters = []; |
||
44 | |||
45 | /** |
||
46 | * @var array<string, Closure> |
||
47 | * |
||
48 | * @psalm-var array<class-string<T>, Closure(string):?string> |
||
49 | */ |
||
50 | protected static $setters = []; |
||
51 | |||
52 | /** |
||
53 | * @psalm-param class-string<T> $type |
||
54 | */ |
||
55 | 24 | public static function IsTypeUnregistered(string $type) : bool |
|
56 | { |
||
57 | 24 | return ! isset(static::$properties[$type]); |
|
58 | } |
||
59 | |||
60 | /** |
||
61 | * @psalm-param class-string<T> $type |
||
62 | * @psalm-param null|Closure(string):?string $getter |
||
63 | * @psalm-param null|Closure(string):?string $setter |
||
64 | */ |
||
65 | 24 | public static function RegisterType( |
|
66 | string $type, |
||
67 | ? Closure $getter, |
||
68 | ? Closure $setter, |
||
69 | string $property, |
||
70 | string ...$properties |
||
71 | ) : void { |
||
72 | 24 | if ( ! self::IsTypeUnregistered($type)) { |
|
73 | 4 | throw new InvalidArgumentException( |
|
74 | 'Argument 1 passed to ' . |
||
75 | __METHOD__ . |
||
76 | 4 | '() has already been registered!' |
|
77 | ); |
||
78 | 24 | } elseif (is_null($getter) && is_null($setter)) { |
|
79 | 4 | throw new InvalidArgumentException( |
|
80 | 4 | 'One or both of arguments 2 and 3 must be specified!' |
|
81 | ); |
||
82 | } |
||
83 | |||
84 | 20 | array_unshift($properties, $property); |
|
85 | |||
86 | 20 | static::MaybeRegisterTypeGetter($type, $getter); |
|
87 | 16 | static::MaybeRegisterTypeSetter($type, $setter); |
|
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||
88 | |||
89 | 12 | static::$properties[$type] = $properties; |
|
90 | 12 | } |
|
91 | |||
92 | /** |
||
93 | * @psalm-param class-string<T> $type |
||
94 | */ |
||
95 | 8 | public static function GetterMethodName(string $type, string $property) : ? string |
|
96 | { |
||
97 | if ( |
||
98 | 8 | in_array($property, static::$properties[$type] ?? [], self::IN_ARRAY_STRICT_MODE) && |
|
99 | 8 | isset(static::$getters[$type]) |
|
100 | ) { |
||
101 | 8 | return static::$getters[$type]($property); |
|
102 | } |
||
103 | |||
104 | 8 | return self::CheckOtherTypes(self::$getters, $type, $property); |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @psalm-param class-string<T> $type |
||
109 | */ |
||
110 | 8 | public static function SetterMethodName(string $type, string $property) : ? string |
|
111 | { |
||
112 | if ( |
||
113 | 8 | in_array($property, static::$properties[$type] ?? [], self::IN_ARRAY_STRICT_MODE) && |
|
114 | 8 | isset(static::$setters[$type]) |
|
115 | ) { |
||
116 | 8 | return static::$setters[$type]($property); |
|
117 | } |
||
118 | |||
119 | 8 | return self::CheckOtherTypes(self::$setters, $type, $property); |
|
120 | } |
||
121 | |||
122 | /** |
||
123 | * @param string|object $maybe |
||
124 | * |
||
125 | * @psalm-param class-string<T>|T $maybe |
||
126 | * |
||
127 | * @return array<int, string> |
||
128 | */ |
||
129 | 12 | public static function ObtainExpectedProperties($maybe) : array |
|
130 | { |
||
131 | /** |
||
132 | * @var array<int, string> |
||
133 | */ |
||
134 | 12 | $out = array_values(array_unique(array_reduce( |
|
135 | 9 | array_filter( |
|
136 | 9 | static::$properties, |
|
137 | function (string $type) use ($maybe) : bool { |
||
138 | 12 | return is_a($maybe, $type, is_string($maybe)); |
|
139 | 12 | }, |
|
140 | 12 | ARRAY_FILTER_USE_KEY |
|
141 | ), |
||
142 | 12 | 'array_merge', |
|
143 | 12 | [] |
|
144 | ))); |
||
145 | |||
146 | 12 | return $out; |
|
147 | } |
||
148 | |||
149 | /** |
||
150 | * @param array<string, Closure> $otherTypes |
||
151 | * |
||
152 | * @psalm-param array<class-string<T>, Closure(string):?string> $otherTypes |
||
153 | * @psalm-param class-string<T> $type |
||
154 | */ |
||
155 | 12 | protected static function CheckOtherTypes( |
|
156 | array $otherTypes, |
||
157 | string $type, |
||
158 | string $property |
||
159 | ) : ? string { |
||
160 | 12 | foreach ($otherTypes as $otherType => $getter) { |
|
161 | if ( |
||
162 | 12 | $otherType !== $type && |
|
163 | 11 | isset(self::$properties[$otherType]) && |
|
164 | 11 | in_array($property, self::$properties[$otherType], self::IN_ARRAY_STRICT_MODE) |
|
165 | ) { |
||
166 | 11 | return $getter($property); |
|
167 | } |
||
168 | } |
||
169 | |||
170 | 8 | return null; |
|
171 | } |
||
172 | |||
173 | /** |
||
174 | * @psalm-param class-string<T> $type |
||
175 | * @psalm-param null|Closure(string):?string $getter |
||
176 | */ |
||
177 | 20 | private static function MaybeRegisterTypeGetter(string $type, ? Closure $getter) : void |
|
178 | { |
||
179 | 20 | if ( ! is_null($getter)) { |
|
180 | 16 | if ( ! method_exists($type, '__get')) { |
|
181 | 4 | throw new InvalidArgumentException( |
|
182 | 'Argument 1 passed to ' . |
||
183 | __CLASS__ . |
||
184 | 4 | '::RegisterType() must declare __get() !' |
|
185 | ); |
||
186 | } |
||
187 | |||
188 | 12 | self::$getters[$type] = $getter; |
|
189 | } |
||
190 | 16 | } |
|
191 | |||
192 | /** |
||
193 | * @psalm-param class-string<T> $type |
||
194 | * @psalm-param null|Closure(string):?string $setter |
||
195 | */ |
||
196 | 16 | private static function MaybeRegisterTypeSetter(string $type, ? Closure $setter) : void |
|
197 | { |
||
198 | 16 | if ( ! is_null($setter)) { |
|
199 | 16 | if ( ! method_exists($type, '__set')) { |
|
200 | 4 | throw new InvalidArgumentException( |
|
201 | 'Argument 1 passed to ' . |
||
202 | __CLASS__ . |
||
203 | 4 | '::RegisterType() must declare __set() !' |
|
204 | ); |
||
205 | } |
||
206 | |||
207 | 12 | self::$setters[$type] = $setter; |
|
208 | } |
||
209 | 12 | } |
|
210 | } |
||
211 |