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.