Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Completed
Push — master ( b177a2...8c4509 )
by Jérémiah
18s queued 11s
created

AbstractTypeGenerator   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 56
eloc 154
dl 0
loc 353
ccs 143
cts 143
cp 1
rs 5.5199
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveTypeCode() 0 3 1
A isExpression() 0 3 2
A generateClassDocBlock() 0 10 1
A generateClassName() 0 3 1
A __construct() 0 4 1
B callableCallbackFromArrayValue() 0 43 11
A types2String() 0 5 1
A generateClasses() 0 12 2
A setExpressionLanguage() 0 6 1
A generateClass() 0 22 6
A varExportFromArrayValue() 0 9 2
A getWrappedType() 0 3 2
A generateParentClassName() 0 3 1
A processFromArray() 0 10 2
A arrayKeyExistsAndIsNotNull() 0 3 2
A generateConfig() 0 7 1
A resolveTypesCode() 0 9 2
A getInternalTypes() 0 3 2
A typeAlias2String() 0 23 5
A getExpressionLanguage() 0 3 1
A generateClosureUseStatements() 0 3 1
B varExport() 0 41 8

How to fix   Complexity   

Complex Class

Complex classes like AbstractTypeGenerator 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.

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 AbstractTypeGenerator, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of the OverblogGraphQLPhpGenerator package.
5
 *
6
 * (c) Overblog <http://github.com/overblog/>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Overblog\GraphQLGenerator\Generator;
13
14
use GraphQL\Type\Definition\CustomScalarType;
15
use GraphQL\Type\Definition\EnumType;
16
use GraphQL\Type\Definition\InputObjectType;
17
use GraphQL\Type\Definition\InterfaceType;
18
use GraphQL\Type\Definition\ObjectType;
19
use GraphQL\Type\Definition\Type;
20
use GraphQL\Type\Definition\UnionType;
21
use Symfony\Component\ExpressionLanguage\Expression;
22
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
23
24
abstract class AbstractTypeGenerator extends AbstractClassGenerator
25
{
26
    public const DEFAULT_CLASS_NAMESPACE = 'Overblog\\CG\\GraphQLGenerator\\__Schema__';
27
28
    protected const DEFERRED_PLACEHOLDERS = ['useStatement', 'spaces', 'closureUseStatements'];
29
30
    protected const CLOSURE_TEMPLATE = <<<EOF
31
function (%s) <closureUseStatements>{
32
<spaces><spaces>return %s;
33
<spaces>}
34
EOF;
35
36
    private const TYPE_SYSTEMS = [
37
        'object' => ObjectType::class,
38
        'interface' => InterfaceType::class,
39
        'enum' => EnumType::class,
40
        'union' => UnionType::class,
41
        'input-object' => InputObjectType::class,
42
        'custom-scalar' => CustomScalarType::class,
43
    ];
44
45
    private const INTERNAL_TYPES = [
46
        Type::STRING => '\\GraphQL\\Type\\Definition\\Type::string()',
47
        Type::INT => '\\GraphQL\\Type\\Definition\\Type::int()',
48
        Type::FLOAT => '\\GraphQL\\Type\\Definition\\Type::float()',
49
        Type::BOOLEAN => '\\GraphQL\\Type\\Definition\\Type::boolean()',
50
        Type::ID => '\\GraphQL\\Type\\Definition\\Type::id()',
51
    ];
52
53
    private const WRAPPED_TYPES = [
54
        'NonNull' => '\\GraphQL\\Type\\Definition\\Type::nonNull',
55
        'ListOf' => '\\GraphQL\\Type\\Definition\\Type::listOf',
56
    ];
57
58
    private $canManageExpressionLanguage = false;
59
60
    /**
61
     * @var null|ExpressionLanguage
62
     */
63
    private $expressionLanguage;
64
65
    /**
66
     * @var int
67
     */
68
    protected $cacheDirMask;
69
70
    /**
71
     * @param string $classNamespace The namespace to use for the classes.
72
     * @param string[]|string $skeletonDirs
73
     * @param int $cacheDirMask
74
     */
75 102
    public function __construct(string $classNamespace = self::DEFAULT_CLASS_NAMESPACE, $skeletonDirs = [], int $cacheDirMask = 0775)
76
    {
77 102
        parent::__construct($classNamespace, $skeletonDirs);
78 102
        $this->cacheDirMask = $cacheDirMask;
79 102
    }
80
81 97
    public function setExpressionLanguage(ExpressionLanguage $expressionLanguage = null): self
82
    {
83 97
        $this->expressionLanguage = $expressionLanguage;
84 97
        $this->canManageExpressionLanguage = null !== $expressionLanguage;
85
86 97
        return $this;
87
    }
88
89 55
    public function getExpressionLanguage(): ExpressionLanguage
90
    {
91 55
        return $this->expressionLanguage;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->expressionLanguage could return the type null which is incompatible with the type-hinted return Symfony\Component\Expres...uage\ExpressionLanguage. Consider adding an additional type-check to rule them out.
Loading history...
92
    }
93
94 61
    public function isExpression($str): bool
95
    {
96 61
        return $this->canManageExpressionLanguage && $str instanceof Expression;
97
    }
98
99 60
    public static function getInternalTypes(string $name): ?string
100
    {
101 60
        return isset(self::INTERNAL_TYPES[$name]) ? self::INTERNAL_TYPES[$name] : null;
102
    }
103
104 60
    public static function getWrappedType(string $name): ?string
105
    {
106 60
        return isset(self::WRAPPED_TYPES[$name]) ? self::WRAPPED_TYPES[$name] : null;
107
    }
108
109 61
    protected function generateParentClassName(array $config): string
110
    {
111 61
        return $this->shortenClassName(self::TYPE_SYSTEMS[$config['type']]);
112
    }
113
114 32
    protected function generateClassName(array $config): string
115
    {
116 32
        return $config['config']['name'].'Type';
117
    }
118
119 29
    protected function generateClassDocBlock(array $config): string
120
    {
121 29
        $className = $this->generateClassName($config);
122 29
        $namespace = $this->getClassNamespace();
123
124
        return <<<EOF
125
126
/**
127 29
 * Class $className
128 29
 * @package $namespace
129
 */
130
EOF;
131
    }
132
133 61
    protected function varExportFromArrayValue(array $values, string $key, string $default = 'null', array $compilerNames = []): string
134
    {
135 61
        if (!isset($values[$key])) {
136 61
            return $default;
137
        }
138
139 61
        $code = $this->varExport($values[$key], $default, $compilerNames);
140
141 61
        return $code;
142
    }
143
144 61
    protected function varExport($var, ?string $default = null, array $compilerNames = []): ?string
145
    {
146
        switch (true) {
147 61
            case \is_array($var):
148 27
                $indexed = \array_keys($var) === \range(0, \count($var) - 1);
149 27
                $r = [];
150 27
                foreach ($var as $key => $value) {
151 27
                    $r[] = ($indexed ? '' : $this->varExport($key, $default).' => ')
152 27
                        .$this->varExport($value, $default);
153
                }
154 27
                return "[".\implode(", ", $r)."]";
155
156 61
            case $this->isExpression($var):
157 23
                return $code = $this->getExpressionLanguage()->compile($var, $compilerNames);
0 ignored issues
show
Unused Code introduced by
The assignment to $code is dead and can be removed.
Loading history...
158
159 61
            case \is_object($var):
160 1
                return $default;
161
162 61
            case \is_string($var):
163 61
                $string = \var_export($var, true);
164
165
                // handle multi-line strings
166 61
                $lines = \explode("\n", $string);
167 61
                if (\count($lines) > 1) {
168 23
                    $firstLine = \sprintf('%s\' . "\n"', \array_shift($lines));
169 23
                    $lastLine = \sprintf("'%s", \array_pop($lines));
170 23
                    $lines = \array_map(
171
                        function ($line) {
172 23
                            return \sprintf('\'%s\' . "\n"', $line);
173 23
                        },
174 23
                        $lines
175
                    );
176 23
                    \array_unshift($lines, $firstLine);
177 23
                    \array_push($lines, $lastLine);
178 23
                    $string = \implode(' . ', $lines);
179
                }
180
181 61
                return $string;
182
183
            default:
184 27
                return \var_export($var, true);
185
        }
186
    }
187
188 61
    protected function processFromArray(array $values, string $templatePrefix)
189
    {
190 61
        $code = '';
191
192 61
        foreach ($values as $name => $value) {
193 61
            $value['name'] = $value['name'] ?? $name;
194 61
            $code .= "\n".$this->processTemplatePlaceHoldersReplacements($templatePrefix.'Config', $value);
195
        }
196
197 60
        return '['.$this->prefixCodeWithSpaces($code, 2)."\n<spaces>]";
198
    }
199
200 60
    protected function callableCallbackFromArrayValue(array $value, string $key, ?string $argDefinitions = null, string $default = 'null', array $compilerNames = null)
201
    {
202 60
        if (!$this->arrayKeyExistsAndIsNotNull($value, $key)) {
203 60
            return $default;
204
        }
205
206 52
        $code = static::CLOSURE_TEMPLATE;
207
208 52
        if (\is_callable($value[$key])) {
209 25
            $func = $value[$key];
210 25
            $code = \sprintf($code, null, '\call_user_func_array(%s, \func_get_args())');
211
212 25
            if (\is_array($func) && isset($func[0]) && \is_string($func[0])) {
213 25
                $code = \sprintf($code, $this->varExport($func));
214
215 25
                return $code;
216 23
            } elseif (\is_string($func)) {
217 23
                $code = \sprintf($code, \var_export($func, true));
218
219 23
                return $code;
220
            }
221 50
        } elseif ($this->isExpression($value[$key])) {
222 48
            if (null === $compilerNames) {
223 48
                $compilerNames = [];
224 48
                if (null !== $argDefinitions) {
225 48
                    \preg_match_all('@\$([a-z_][a-z0-9_]+)@i', $argDefinitions, $matches);
226 48
                    $compilerNames = $matches[1] ?? [];
227
                }
228
            }
229 48
            $code = \sprintf(
230 48
                $code,
231 48
                $this->shortenClassFromCode($argDefinitions),
232 48
                $this->getExpressionLanguage()->compile($value[$key], $compilerNames)
233
            );
234
235 48
            return $code;
236 19
        } elseif (!\is_object($value[$key])) {
237 19
            $code = \sprintf($code, null, $this->varExportFromArrayValue($value, $key, $default));
238
239 19
            return $code;
240
        }
241
242 1
        return $default;
243
    }
244
245 61
    protected function generateConfig(array $config)
246
    {
247 61
        $template = \str_replace(' ', '', \ucwords(\str_replace('-', ' ', $config['type']))).'Config';
248 61
        $code = $this->processTemplatePlaceHoldersReplacements($template, $config['config']);
249 60
        $code = \ltrim($this->prefixCodeWithSpaces($code, 2));
250
251 60
        return $code;
252
    }
253
254 28
    protected function generateClosureUseStatements(array $config): ?string
0 ignored issues
show
Unused Code introduced by
The parameter $config is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

254
    protected function generateClosureUseStatements(/** @scrutinizer ignore-unused */ array $config): ?string

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
255
    {
256 28
        return null;
257
    }
258
259 61
    protected function typeAlias2String($alias): string
260
    {
261
        // Non-Null
262 61
        if ('!' === $alias[\strlen($alias) - 1]) {
263 59
            return \sprintf('%s(%s)', $this->shortenClassName(static::getWrappedType('NonNull')), $this->typeAlias2String(\substr($alias, 0, -1)));
0 ignored issues
show
Bug introduced by
It seems like static::getWrappedType('NonNull') can also be of type null; however, parameter $definition of Overblog\GraphQLGenerato...tor::shortenClassName() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

263
            return \sprintf('%s(%s)', $this->shortenClassName(/** @scrutinizer ignore-type */ static::getWrappedType('NonNull')), $this->typeAlias2String(\substr($alias, 0, -1)));
Loading history...
264
        }
265
        // List
266 61
        if ('[' === $alias[0]) {
267 48
            $got = $alias[\strlen($alias) - 1];
268 48
            if (']' !== $got) {
269 1
                throw new \RuntimeException(
270 1
                    \sprintf('Malformed ListOf wrapper type %s expected "]" but got %s.', \json_encode($alias), \json_encode($got))
271
                );
272
            }
273
274 47
            return \sprintf('%s(%s)', $this->shortenClassName(static::getWrappedType('ListOf')), $this->typeAlias2String(\substr($alias, 1, -1)));
275
        }
276
277 60
        if (null !== ($systemType = static::getInternalTypes($alias))) {
278 60
            return $this->shortenClassName($systemType);
279
        }
280
281 49
        return $this->resolveTypeCode($alias);
282
    }
283
284 23
    protected function resolveTypeCode(string $alias): string
285
    {
286 23
        return $alias.'Type::getInstance()';
287
    }
288
289 60
    protected function resolveTypesCode(array $values, string $key): string
290
    {
291 60
        if (isset($values[$key])) {
292 55
            $types = \sprintf(static::CLOSURE_TEMPLATE, '', $this->types2String($values[$key]));
293
        } else {
294 28
            $types = '[]';
295
        }
296
297 60
        return  $types;
298
    }
299
300 55
    protected function types2String(array $types): string
301
    {
302 55
        $types = \array_map(__CLASS__.'::typeAlias2String', $types);
303
304 55
        return '['.\implode(', ', $types).']';
305
    }
306
307 60
    protected function arrayKeyExistsAndIsNotNull(array $value, $key): bool
308
    {
309 60
        return \array_key_exists($key, $value) && null !== $value[$key];
310
    }
311
312
    /**
313
     * Configs has the following structure:
314
     * <code>
315
     * [
316
     *     [
317
     *         'type' => 'object', // the file type
318
     *         'config' => [], // the class config
319
     *     ],
320
     *     [
321
     *         'type' => 'interface',
322
     *         'config' => [],
323
     *     ],
324
     *     //...
325
     * ]
326
     * </code>
327
     *
328
     * @param array    $configs
329
     * @param string   $outputDirectory
330
     * @param int $mode
331
     *
332
     * @return array
333
     */
334 65
    public function generateClasses(array $configs, ?string $outputDirectory, int $mode = self::MODE_WRITE): array
335
    {
336 65
        $classesMap = [];
337
338 65
        foreach ($configs as $name => $config) {
339 65
            $config['config']['name'] = $config['config']['name'] ?? $name;
340 65
            $classMap = $this->generateClass($config, $outputDirectory, $mode);
341
342 63
            $classesMap = \array_merge($classesMap, $classMap);
343
        }
344
345 63
        return $classesMap;
346
    }
347
348
    /**
349
     * @param array    $config
350
     * @param string   $outputDirectory
351
     * @param int      $mode
352
     *
353
     * @return array
354
     */
355 65
    public function generateClass(array $config, ?string $outputDirectory, int $mode = self::MODE_WRITE): array
356
    {
357 65
        $className = $this->generateClassName($config);
358 65
        $path = $outputDirectory.'/'.$className.'.php';
359
360 65
        if (!($mode & self::MODE_MAPPING_ONLY)) {
361 63
            $this->clearInternalUseStatements();
362 63
            $code = $this->processTemplatePlaceHoldersReplacements('TypeSystem', $config, static::DEFERRED_PLACEHOLDERS);
363 61
            $code = $this->processPlaceHoldersReplacements(static::DEFERRED_PLACEHOLDERS, $code, $config)."\n";
364
365 61
            if ($mode & self::MODE_WRITE) {
366 60
                $dir = \dirname($path);
367 60
                if (!\is_dir($dir)) {
368 60
                    \mkdir($dir, $this->cacheDirMask, true);
369
                }
370 60
                if (($mode & self::MODE_OVERRIDE) || !\file_exists($path)) {
371 60
                    \file_put_contents($path, $code);
372
                }
373
            }
374
        }
375
376 63
        return [$this->getClassNamespace().'\\'.$className => $path];
377
    }
378
}
379