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 — annotations ( a85f92...4ec00b )
by Jérémiah
24:35 queued 10s
created

AbstractClassGenerator   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 381
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 56
eloc 131
dl 0
loc 381
rs 5.5199
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A generateUseStatement() 0 8 1
A addTrait() 0 8 2
A setNumSpaces() 0 6 1
A clearTraits() 0 5 1
A clearImplements() 0 5 1
A addUseStatement() 0 8 2
A generateNamespace() 0 3 2
A generateClassType() 0 3 1
A setClassNamespace() 0 5 1
A generateImplements() 0 3 2
A prefixCodeWithSpaces() 0 11 3
A __construct() 0 5 1
A shortenClassName() 0 12 2
A clearUseStatements() 0 5 1
A processTemplatePlaceHoldersReplacements() 0 7 1
A generateTraits() 0 5 2
A addSkeletonDir() 0 16 5
A generateSpaces() 0 3 1
A getSkeletonDirs() 0 9 2
A tokenizeUseStatements() 0 13 3
A processPlaceHoldersReplacements() 0 22 3
A getClassNamespace() 0 3 1
A addImplement() 0 8 2
A getSkeletonContent() 0 25 4
A addInternalUseStatement() 0 5 2
A setSkeletonDirs() 0 19 5
A clearInternalUseStatements() 0 5 1
A shortenClassFromCode() 0 10 1
A getPlaceHolders() 0 5 2

How to fix   Complexity   

Complex Class

Complex classes like AbstractClassGenerator 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 AbstractClassGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
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 Overblog\GraphQLGenerator\ClassUtils;
15
use Symfony\Component\ExpressionLanguage\Expression;
16
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17
18
abstract class AbstractClassGenerator
19
{
20
    const SKELETON_FILE_PREFIX = '.php.skeleton';
21
22
    /**
23
     * The namespace that contains all classes.
24
     *
25
     * @var string
26
     */
27
    private $classNamespace;
28
29
    private $internalUseStatements = [];
30
31
    private $useStatements = [];
32
33
    private $traits = [];
34
35
    private $implements = [];
36
37
    private $skeletonDirs = [];
38
39
    /**
40
     * Number of spaces to use for indention in generated code.
41
     */
42
    private $numSpaces;
43
44
    /**
45
     * The actual spaces to use for indention.
46
     *
47
     * @var string
48
     */
49
    private $spaces;
50
51
    private static $templates = [];
52
53
    /**
54
     * @param string $classNamespace The namespace to use for the classes.
55
     * @param string[]|string $skeletonDirs
56
     */
57
    public function __construct($classNamespace = null, $skeletonDirs = [])
58
    {
59
        $this->setClassNamespace($classNamespace);
60
        $this->setSkeletonDirs($skeletonDirs);
61
        $this->setNumSpaces(4);
62
    }
63
64
    public function getClassNamespace()
65
    {
66
        return $this->classNamespace;
67
    }
68
69
    public function setClassNamespace($classNamespace)
70
    {
71
        $this->classNamespace = ClassUtils::cleanClasseName($classNamespace);
72
73
        return $this;
74
    }
75
76
    /**
77
     * @param string[]|string $skeletonDirs
78
     * @return $this
79
     */
80
    public function setSkeletonDirs($skeletonDirs)
81
    {
82
        $this->skeletonDirs = [];
83
84
        if (\is_string($skeletonDirs)) {
85
            $this->addSkeletonDir($skeletonDirs);
86
        } else {
87
            if (!\is_array($skeletonDirs) && !$skeletonDirs instanceof \Traversable) {
0 ignored issues
show
introduced by
The condition is_array($skeletonDirs) is always true.
Loading history...
88
                throw new \InvalidArgumentException(
89
                    \sprintf('Skeleton dirs must be array or object implementing \Traversable interface, "%s" given.', \gettype($skeletonDirs))
90
                );
91
            }
92
93
            foreach ($skeletonDirs as $skeletonDir) {
94
                $this->addSkeletonDir($skeletonDir);
95
            }
96
        }
97
98
        return $this;
99
    }
100
101
    public function getSkeletonDirs($withDefault = true)
102
    {
103
        $skeletonDirs = $this->skeletonDirs ;
104
105
        if ($withDefault) {
106
            $skeletonDirs[] = __DIR__.'/../Resources/skeleton';
107
        }
108
109
        return $skeletonDirs;
110
    }
111
112
    public function addSkeletonDir($skeletonDir)
113
    {
114
        if (!\is_string($skeletonDir) && !\is_object($skeletonDir) && !\is_callable($skeletonDir, '__toString')) {
0 ignored issues
show
Bug introduced by
'__toString' of type string is incompatible with the type boolean expected by parameter $syntax_only of is_callable(). ( Ignorable by Annotation )

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

114
        if (!\is_string($skeletonDir) && !\is_object($skeletonDir) && !\is_callable($skeletonDir, /** @scrutinizer ignore-type */ '__toString')) {
Loading history...
115
            throw new \InvalidArgumentException(
116
                \sprintf('Skeleton dir must be string or object implementing __toString, "%s" given.', \gettype($skeletonDir))
117
            );
118
        }
119
120
        $skeletonDir = (string) $skeletonDir;
121
122
        if (!\is_dir($skeletonDir)) {
123
            throw new \InvalidArgumentException(\sprintf('Skeleton dir "%s" not found.', $skeletonDir));
124
        }
125
        $this->skeletonDirs[] = \realpath($skeletonDir);
126
127
        return $this;
128
    }
129
130
131
    /**
132
     * Sets the number of spaces the exported class should have.
133
     *
134
     * @param integer $numSpaces
135
     *
136
     * @return self
137
     */
138
    public function setNumSpaces($numSpaces)
139
    {
140
        $this->spaces = \str_repeat(' ', $numSpaces);
141
        $this->numSpaces = $numSpaces;
142
143
        return $this;
144
    }
145
146
    public function addTrait($trait)
147
    {
148
        $cleanTrait = $this->shortenClassName($trait, false);
149
        if (!\in_array($cleanTrait, $this->traits)) {
150
            $this->traits[] = $cleanTrait;
151
        }
152
153
        return $this;
154
    }
155
156
    public function clearTraits()
157
    {
158
        $this->traits = [];
159
160
        return $this;
161
    }
162
163
    public function addImplement($implement)
164
    {
165
        $cleanImplement = $this->shortenClassName($implement, false);
166
        if (!\in_array($cleanImplement, $this->implements)) {
167
            $this->implements[] = $cleanImplement;
168
        }
169
170
        return $this;
171
    }
172
173
    public function clearImplements()
174
    {
175
        $this->implements = [];
176
177
        return $this;
178
    }
179
180
    public function addUseStatement($useStatement)
181
    {
182
        $cleanUse = ClassUtils::cleanClasseName($useStatement);
183
        if (!\in_array($cleanUse, $this->useStatements)) {
184
            $this->useStatements[] = $cleanUse;
185
        }
186
187
        return $this;
188
    }
189
190
    public function clearUseStatements()
191
    {
192
        $this->useStatements = [];
193
194
        return $this;
195
    }
196
197
    public function getSkeletonContent($skeleton, $withDefault = true)
198
    {
199
        $skeletonDirs = $this->getSkeletonDirs($withDefault);
200
201
        foreach ($skeletonDirs as $skeletonDir) {
202
            $path = $skeletonDir.'/'.$skeleton.static::SKELETON_FILE_PREFIX;
203
204
            if (!\file_exists($path)) {
205
                continue;
206
            }
207
208
            if (!isset(self::$templates[$path])) {
209
                $content = \trim(\file_get_contents($path));
210
211
                self::$templates[$path] = $content;
212
            }
213
214
            return self::$templates[$path];
215
        }
216
217
        throw new \InvalidArgumentException(
218
            \sprintf(
219
                'Skeleton "%s" could not be found in %s.',
220
                $skeleton,
221
                \implode(', ', $skeletonDirs)
222
            )
223
        );
224
    }
225
226
    protected function addInternalUseStatement($use)
227
    {
228
        $cleanUse = ClassUtils::cleanClasseName($use);
229
        if (!\in_array($cleanUse, $this->internalUseStatements)) {
230
            $this->internalUseStatements[] = $cleanUse;
231
        }
232
    }
233
234
    protected function clearInternalUseStatements()
235
    {
236
        $this->internalUseStatements = [];
237
238
        return $this;
239
    }
240
241
    protected function shortenClassName($definition, $isInternal = true)
242
    {
243
        $shortName = ClassUtils::shortenClassName($definition);
244
245
        $useStatement = \preg_replace('@\:\:.*$@i', '', $definition);
246
        if ($isInternal) {
247
            $this->addInternalUseStatement($useStatement);
248
        } else {
249
            $this->addUseStatement($useStatement);
250
        }
251
252
        return $shortName;
253
    }
254
255
    protected function shortenClassFromCode($code)
256
    {
257
        $codeParsed = ClassUtils::shortenClassFromCode(
258
            $code,
259
            function ($matches) {
260
                return $this->shortenClassName($matches[1]);
261
            }
262
        );
263
264
        return $codeParsed;
265
    }
266
267
    protected function processPlaceHoldersReplacements(array $placeHolders, $content, array $values)
268
    {
269
        $replacements = [];
270
271
        foreach ($placeHolders as $placeHolder) {
272
            $generator = [$this, 'generate'.\ucfirst($placeHolder)];
273
            $name = '<'.$placeHolder.'>';
274
275
            if (\is_callable($generator)) {
276
                $replacements[$name] = \call_user_func_array($generator, [$values]);
277
            } else {
278
                throw new \RuntimeException(
279
                    \sprintf(
280
                        'Generator [%s] for placeholder "%s" is not callable.',
281
                        \get_class($generator[0]).'::'.$generator[1],
282
                        $placeHolder
283
                    )
284
                );
285
            }
286
        }
287
288
        return \strtr($content, $replacements);
289
    }
290
291
    protected function processTemplatePlaceHoldersReplacements($template, array $values, array $skip = [])
292
    {
293
        $code = $this->getSkeletonContent($template);
294
        $placeHolders = $this->getPlaceHolders($code);
295
        $code = $this->processPlaceHoldersReplacements(\array_diff($placeHolders, $skip), $code, $values);
296
297
        return $code;
298
    }
299
300
    protected function getPlaceHolders($content)
301
    {
302
        \preg_match_all('@<([\w]+)>@i', $content, $placeHolders);
303
304
        return isset($placeHolders[1]) ? $placeHolders[1] : [];
305
    }
306
307
    /**
308
     * @param string $code
309
     * @param int $num
310
     *
311
     * @return string
312
     */
313
    protected function prefixCodeWithSpaces($code, $num = 1)
314
    {
315
        $lines = \explode("\n", $code);
316
317
        foreach ($lines as $key => $value) {
318
            if (!empty($value)) {
319
                $lines[$key] = \str_repeat($this->spaces, $num).$lines[$key];
320
            }
321
        }
322
323
        return \implode("\n", $lines);
324
    }
325
326
    protected function generateSpaces()
327
    {
328
        return $this->spaces;
329
    }
330
331
    protected function generateNamespace()
332
    {
333
        return null !== $this->classNamespace ? 'namespace '.$this->classNamespace.';' : null;
334
    }
335
336
    protected function generateUseStatement(array $config)
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

336
    protected function generateUseStatement(/** @scrutinizer ignore-unused */ array $config)

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...
337
    {
338
        $statements = \array_merge($this->internalUseStatements, $this->useStatements);
339
        \sort($statements);
340
341
        $useStatements = $this->tokenizeUseStatements($statements);
342
343
        return $useStatements;
344
    }
345
346
    protected function generateClassType()
347
    {
348
        return 'final ';
349
    }
350
351
    protected function generateImplements()
352
    {
353
        return \count($this->implements) ? ' implements '.\implode(', ', $this->implements) : null;
354
    }
355
356
    protected function generateTraits()
357
    {
358
        $traits = $this->tokenizeUseStatements($this->traits, '<spaces>');
359
360
        return $traits ? $traits."\n" : $traits;
361
    }
362
363
    protected function tokenizeUseStatements(array $useStatements, $prefix = '')
364
    {
365
        if (empty($useStatements)) {
366
            return null;
367
        }
368
369
        $code = '';
370
371
        foreach ($useStatements as $useStatement) {
372
            $code .= "\n${prefix}use $useStatement;";
373
        }
374
375
        return $code;
376
    }
377
378
    /**
379
     * Generates classes files.
380
     *
381
     * @param array    $configs raw configs
382
     * @param string   $outputDirectory
383
     * @param int|bool $mode
384
     *
385
     * @return array classes map [[FQCLN => classPath], [FQCLN => classPath], ...]
386
     */
387
    abstract public function generateClasses(array $configs, $outputDirectory, $mode = false);
388
389
    /**
390
     * Generates a class file.
391
     *
392
     * @param array $config
393
     * @param $outputDirectory
394
     * @param bool $mode
395
     *
396
     * @return array classes map [FQCLN => classPath]
397
     */
398
    abstract public function generateClass(array $config, $outputDirectory, $mode = false);
399
}
400