SignpostMarv /
daft-magic-property-analysis
| 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
Loading history...
|
|||
| 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 |