We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
Complex classes like ConfigResolver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ConfigResolver, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 25 | class ConfigResolver implements ResolverInterface |
||
| 26 | { |
||
| 27 | /** |
||
| 28 | * @var ExpressionLanguage |
||
| 29 | */ |
||
| 30 | private $expressionLanguage; |
||
| 31 | |||
| 32 | /** |
||
| 33 | * @var TypeResolver |
||
| 34 | */ |
||
| 35 | private $typeResolver; |
||
| 36 | |||
| 37 | /** |
||
| 38 | * @var FieldResolver |
||
| 39 | */ |
||
| 40 | private $fieldResolver; |
||
| 41 | |||
| 42 | /** |
||
| 43 | * @var ArgResolver |
||
| 44 | */ |
||
| 45 | private $argResolver; |
||
| 46 | |||
| 47 | /** |
||
| 48 | * @var callable |
||
| 49 | */ |
||
| 50 | private $defaultResolveFn = ['GraphQL\Executor\Executor', 'defaultResolveFn']; |
||
| 51 | |||
| 52 | /** |
||
| 53 | * @var array |
||
| 54 | * [name => callable] |
||
| 55 | */ |
||
| 56 | private $resolverMap = []; |
||
| 57 | |||
| 58 | 32 | public function __construct( |
|
| 89 | |||
| 90 | 25 | public function setDefaultResolveFn(callable $defaultResolveFn) |
|
| 91 | { |
||
| 92 | 25 | Executor::setDefaultResolveFn($defaultResolveFn); |
|
| 93 | |||
| 94 | 25 | $this->defaultResolveFn = $defaultResolveFn; |
|
| 95 | 25 | } |
|
| 96 | |||
| 97 | public function addResolverMap($name, callable $resolver) |
||
| 101 | |||
| 102 | 27 | public function resolve($config) |
|
| 103 | { |
||
| 104 | 27 | if (!is_array($config) || $config instanceof \ArrayAccess) { |
|
| 105 | 1 | throw new \RuntimeException('Config must be an array or implement \ArrayAccess interface'); |
|
| 106 | } |
||
| 107 | |||
| 108 | 26 | foreach ($config as $name => &$values) { |
|
| 109 | 26 | if (!isset($this->resolverMap[$name]) || empty($values)) { |
|
| 110 | 25 | continue; |
|
| 111 | } |
||
| 112 | 26 | $values = call_user_func_array($this->resolverMap[$name], [$values]); |
|
| 113 | 26 | } |
|
| 114 | |||
| 115 | 26 | return $config; |
|
| 116 | } |
||
| 117 | |||
| 118 | 25 | private function resolveFields(array $fields) |
|
| 119 | { |
||
| 120 | 25 | foreach ($fields as $field => &$options) { |
|
| 121 | 25 | if (isset($options['builder']) && is_string($options['builder'])) { |
|
| 122 | 19 | $alias = $options['builder']; |
|
| 123 | |||
| 124 | 19 | $fieldBuilder = $this->fieldResolver->resolve($alias); |
|
| 125 | 19 | $builderConfig = []; |
|
| 126 | 19 | if (isset($options['builderConfig'])) { |
|
| 127 | 19 | if (!is_array($options['builderConfig'])) { |
|
| 128 | $options['builderConfig'] = [$options['builderConfig']]; |
||
| 129 | } |
||
| 130 | 19 | $builderConfig = $this->resolve($options['builderConfig']); |
|
| 131 | 19 | } |
|
| 132 | 19 | $builderConfig['name'] = $field; |
|
| 133 | |||
| 134 | 19 | $access = isset($options['access']) ? $options['access'] : null; |
|
| 135 | |||
| 136 | 19 | if ($fieldBuilder instanceof FieldInterface) { |
|
| 137 | 19 | $options = $fieldBuilder->toFieldDefinition($builderConfig); |
|
| 138 | 19 | } elseif (is_callable($fieldBuilder)) { |
|
| 139 | $options = call_user_func_array($fieldBuilder, [$builderConfig]); |
||
| 140 | } elseif (is_object($fieldBuilder)) { |
||
| 141 | $options = get_object_vars($fieldBuilder); |
||
| 142 | } else { |
||
| 143 | throw new \RuntimeException(sprintf('Could not build field "%s".', $alias)); |
||
| 144 | } |
||
| 145 | |||
| 146 | 19 | $options['access'] = $access; |
|
| 147 | 19 | $options = $this->resolveResolveAndAccessIfNeeded($options); |
|
| 148 | |||
| 149 | 19 | unset($options['builderConfig'], $options['builder']); |
|
| 150 | |||
| 151 | 19 | continue; |
|
| 152 | } |
||
| 153 | |||
| 154 | 25 | if (isset($options['type'])) { |
|
| 155 | 25 | $options['type'] = $this->resolveTypeCallback($options['type']); |
|
| 156 | 25 | } |
|
| 157 | |||
| 158 | 25 | if (isset($options['args'])) { |
|
| 159 | foreach ($options['args'] as &$argsOptions) { |
||
| 160 | $argsOptions['type'] = $this->resolveTypeCallback($argsOptions['type']); |
||
| 161 | if (isset($argsOptions['defaultValue'])) { |
||
| 162 | $argsOptions['defaultValue'] = $this->resolveUsingExpressionLanguageIfNeeded($argsOptions['defaultValue']); |
||
| 163 | } |
||
| 164 | } |
||
| 165 | } |
||
| 166 | |||
| 167 | 25 | if (isset($options['argsBuilder'])) { |
|
| 168 | 6 | $alias = $options['argsBuilder']['name']; |
|
| 169 | |||
| 170 | 6 | $argsBuilder = $this->argResolver->resolve($alias); |
|
| 171 | 6 | $argsBuilderConfig = []; |
|
| 172 | 6 | if (isset($options['argsBuilder']['config'])) { |
|
| 173 | if (!is_array($options['argsBuilder']['config'])) { |
||
| 174 | $options['argsBuilder']['config'] = [$options['argsBuilder']['config']]; |
||
| 175 | } |
||
| 176 | $argsBuilderConfig = $this->resolve($options['argsBuilder']['config']); |
||
| 177 | } |
||
| 178 | |||
| 179 | 6 | $options['args'] = isset($options['args']) ? $options['args'] : []; |
|
| 180 | |||
| 181 | 6 | if ($argsBuilder instanceof ArgsInterface) { |
|
| 182 | 6 | $options['args'] = array_merge($argsBuilder->toArgsDefinition($argsBuilderConfig), $options['args']); |
|
| 183 | 6 | } elseif (is_callable($argsBuilder)) { |
|
| 184 | $options['args'] = array_merge(call_user_func_array($argsBuilder, [$argsBuilderConfig]), $options['args']); |
||
| 185 | } elseif (is_object($argsBuilder)) { |
||
| 186 | $options['args'] = array_merge(get_object_vars($argsBuilder), $options['args']); |
||
| 187 | } else { |
||
| 188 | throw new \RuntimeException(sprintf('Could not build args "%s".', $alias)); |
||
| 189 | } |
||
| 190 | |||
| 191 | 6 | unset($options['argsBuilder']); |
|
| 192 | 6 | } |
|
| 193 | |||
| 194 | 25 | $options = $this->resolveResolveAndAccessIfNeeded($options); |
|
| 195 | |||
| 196 | 25 | if (isset($options['deprecationReason'])) { |
|
| 197 | $options['deprecationReason'] = $this->resolveUsingExpressionLanguageIfNeeded($options['deprecationReason']); |
||
| 198 | } |
||
| 199 | 25 | } |
|
| 200 | |||
| 201 | 25 | return $fields; |
|
| 202 | } |
||
| 203 | |||
| 204 | 25 | private function resolveResolveAndAccessIfNeeded(array $options) |
|
| 205 | { |
||
| 206 | 25 | $treatedOptions = $options; |
|
| 207 | |||
| 208 | 25 | if (isset($treatedOptions['resolve'])) { |
|
| 209 | 25 | $treatedOptions['resolve'] = $this->resolveResolveCallback($treatedOptions['resolve']); |
|
| 210 | 25 | } |
|
| 211 | |||
| 212 | 25 | if (isset($treatedOptions['access'])) { |
|
| 213 | $resolveCallback = $this->defaultResolveFn; |
||
| 214 | |||
| 215 | if (isset($treatedOptions['resolve'])) { |
||
| 216 | $resolveCallback = $treatedOptions['resolve']; |
||
| 217 | } |
||
| 218 | |||
| 219 | $treatedOptions['resolve'] = $this->resolveAccessAndWrapResolveCallback($treatedOptions['access'], $resolveCallback); |
||
| 220 | } |
||
| 221 | 25 | unset($treatedOptions['access']); |
|
| 222 | |||
| 223 | 25 | return $treatedOptions; |
|
| 224 | } |
||
| 225 | |||
| 226 | 25 | private function resolveTypeCallback($expr) |
|
| 227 | { |
||
| 228 | return function () use ($expr) { |
||
| 229 | 25 | return $this->resolveType($expr); |
|
| 230 | 25 | }; |
|
| 231 | } |
||
| 232 | |||
| 233 | 11 | private function resolveInterfaces(array $rawInterfaces) |
|
| 234 | { |
||
| 235 | 11 | return $this->resolveTypes($rawInterfaces, 'GraphQL\\Type\\Definition\\InterfaceType'); |
|
| 236 | } |
||
| 237 | |||
| 238 | 11 | private function resolveTypes(array $rawTypes, $parentClass = 'GraphQL\\Type\\Definition\\Type') |
|
| 239 | { |
||
| 240 | 11 | $types = []; |
|
| 241 | |||
| 242 | 11 | foreach ($rawTypes as $alias) { |
|
| 243 | 11 | $types[] = $this->resolveType($alias, $parentClass); |
|
| 244 | 11 | } |
|
| 245 | |||
| 246 | 11 | return $types; |
|
| 247 | } |
||
| 248 | |||
| 249 | 25 | private function resolveType($expr, $parentClass = 'GraphQL\\Type\\Definition\\Type') |
|
| 250 | { |
||
| 251 | 25 | $type = $this->typeResolver->resolve($expr); |
|
| 252 | |||
| 253 | 25 | if (class_exists($parentClass) && !$type instanceof $parentClass) { |
|
| 254 | throw new \InvalidArgumentException( |
||
| 255 | sprintf('Invalid type! Must be instance of "%s"', $parentClass) |
||
| 256 | ); |
||
| 257 | } |
||
| 258 | |||
| 259 | 25 | return $type; |
|
| 260 | } |
||
| 261 | |||
| 262 | 5 | private function resolveAccessAndWrapResolveCallback($expression, callable $resolveCallback = null) |
|
| 319 | |||
| 320 | 25 | private function resolveResolveCallback($value) |
|
| 321 | { |
||
| 322 | 25 | if (is_callable($value)) { |
|
| 323 | 19 | return $value; |
|
| 324 | } |
||
| 325 | |||
| 326 | 25 | return function () use ($value) { |
|
| 327 | 18 | $args = func_get_args(); |
|
| 328 | 18 | $result = $this->resolveUsingExpressionLanguageIfNeeded( |
|
| 329 | 18 | $value, |
|
| 330 | 18 | call_user_func_array([$this, 'resolveResolveCallbackArgs'], $args) |
|
| 331 | 18 | ); |
|
| 332 | |||
| 333 | 18 | return $result; |
|
| 334 | 25 | }; |
|
| 335 | } |
||
| 336 | |||
| 337 | 23 | private function resolveResolveCallbackArgs() |
|
| 359 | |||
| 360 | 1 | private function resolveValues(array $rawValues) |
|
| 372 | |||
| 373 | 24 | private function resolveUsingExpressionLanguageIfNeeded($expression, array $values = []) |
|
| 381 | } |
||
| 382 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.
Either this assignment is in error or an instanceof check should be added for that assignment.