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
Pull Request — master (#756)
by
unknown
08:52
created

ConfigParserPass   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Test Coverage

Coverage 98.17%

Importance

Changes 0
Metric Value
wmc 33
eloc 118
dl 0
loc 245
ccs 107
cts 109
cp 0.9817
rs 9.76
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 4 1
A mappingFromBundles() 0 19 3
B detectFilesByTypes() 0 33 7
A parseTypeConfigFiles() 0 22 4
A getConfigs() 0 31 4
A checkTypesDuplication() 0 10 2
A getAlias() 0 3 1
A bundleDir() 0 5 1
A processConfiguration() 0 3 1
A mappingConfig() 0 27 5
A typesNeedPreParsing() 0 8 2
A getAliasPrefix() 0 3 1
A detectFilesFromTypesMappings() 0 10 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Overblog\GraphQLBundle\DependencyInjection\Compiler;
6
7
use InvalidArgumentException;
8
use Overblog\GraphQLBundle\Config\Parser\AnnotationParser;
9
use Overblog\GraphQLBundle\Config\Parser\GraphQLParser;
10
use Overblog\GraphQLBundle\Config\Parser\PreParserInterface;
11
use Overblog\GraphQLBundle\Config\Parser\XmlParser;
12
use Overblog\GraphQLBundle\Config\Parser\YamlParser;
13
use Overblog\GraphQLBundle\DependencyInjection\TypesConfiguration;
14
use Overblog\GraphQLBundle\OverblogGraphQLBundle;
15
use ReflectionClass;
16
use ReflectionException;
17
use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException;
18
use Symfony\Component\Config\Definition\Processor;
19
use Symfony\Component\Config\Resource\FileResource;
20
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21
use Symfony\Component\DependencyInjection\ContainerBuilder;
22
use Symfony\Component\Finder\Finder;
23
use Symfony\Component\Finder\SplFileInfo;
24
use function array_count_values;
25
use function array_filter;
26
use function array_keys;
27
use function array_map;
28
use function array_merge;
29
use function array_replace_recursive;
30
use function call_user_func;
31
use function dirname;
32
use function implode;
33
use function is_a;
34
use function is_dir;
35
use function sprintf;
36
37
class ConfigParserPass implements CompilerPassInterface
38
{
39
    public const SUPPORTED_TYPES_EXTENSIONS = [
40
        'yaml' => '{yaml,yml}',
41
        'xml' => 'xml',
42
        'graphql' => '{graphql,graphqls}',
43
        'annotation' => 'php',
44
    ];
45
46
    public const PARSERS = [
47
        'yaml' => YamlParser::class,
48
        'xml' => XmlParser::class,
49
        'graphql' => GraphQLParser::class,
50
        'annotation' => AnnotationParser::class,
51
    ];
52
53
    private static array $defaultDefaultConfig = [
54
        'definitions' => [
55
            'mappings' => [
56
                'auto_discover' => [
57
                    'root_dir' => true,
58
                    'bundles' => true,
59
                    'built_in' => true,
60
                ],
61
                'types' => [],
62
            ],
63
        ],
64
    ];
65
66
    private array $treatedFiles = [];
67
    private array $preTreatedFiles = [];
68
69
    public const DEFAULT_TYPES_SUFFIX = '.types';
70
71 40
    public function process(ContainerBuilder $container): void
72
    {
73 40
        $config = $this->processConfiguration([$this->getConfigs($container)]);
74 37
        $container->setParameter($this->getAlias().'.config', $config);
75 37
    }
76
77 48
    public function processConfiguration(array $configs): array
78
    {
79 48
        return (new Processor())->processConfiguration(new TypesConfiguration(), $configs);
80
    }
81
82 40
    private function getConfigs(ContainerBuilder $container): array
83
    {
84 40
        $config = $container->getParameterBag()->resolveValue($container->getParameter('overblog_graphql.config'));
85 40
        $container->getParameterBag()->remove('overblog_graphql.config');
86 40
        $container->setParameter($this->getAlias().'.classes_map', []);
87 40
        $typesMappings = $this->mappingConfig($config, $container);
88
        // reset treated files
89 40
        $this->treatedFiles = [];
90 40
        $typesMappings = array_merge(...$typesMappings);
91 40
        $typeConfigs = [];
92
93
        // treats mappings
94
        // Pre-parse all files
95 40
        AnnotationParser::reset();
96 40
        $typesNeedPreParsing = $this->typesNeedPreParsing();
97 40
        foreach ($typesMappings as $params) {
98 39
            if ($typesNeedPreParsing[$params['type']]) {
99 1
                $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config, true);
100
            }
101
        }
102
103
        // Parse all files and get related config
104 40
        foreach ($typesMappings as $params) {
105 39
            $typeConfigs = array_merge($typeConfigs, $this->parseTypeConfigFiles($params['type'], $params['files'], $container, $config));
106
        }
107
108 38
        $this->checkTypesDuplication($typeConfigs);
109
        // flatten config is a requirement to support inheritance
110 38
        $flattenTypeConfig = array_merge(...$typeConfigs);
111
112 38
        return $flattenTypeConfig;
113
    }
114
115 40
    private function typesNeedPreParsing(): array
116
    {
117 40
        $needPreParsing = [];
118 40
        foreach (self::PARSERS as $type => $className) {
119 40
            $needPreParsing[$type] = is_a($className, PreParserInterface::class, true);
120
        }
121
122 40
        return $needPreParsing;
123
    }
124
125
    /**
126
     * @param SplFileInfo[] $files
127
     */
128 39
    private function parseTypeConfigFiles(string $type, iterable $files, ContainerBuilder $container, array $configs, bool $preParse = false): array
129
    {
130 39
        if ($preParse) {
131 1
            $method = 'preParse';
132 1
            $treatedFiles = &$this->preTreatedFiles;
133
        } else {
134 39
            $method = 'parse';
135 39
            $treatedFiles = &$this->treatedFiles;
136
        }
137
138 39
        $config = [];
139 39
        foreach ($files as $file) {
140 39
            $fileRealPath = $file->getRealPath();
141 39
            if (isset($treatedFiles[$fileRealPath])) {
142 1
                continue;
143
            }
144
145 39
            $config[] = call_user_func([self::PARSERS[$type], $method], $file, $container, $configs);
146 37
            $treatedFiles[$file->getRealPath()] = true;
147
        }
148
149 37
        return $config;
150
    }
151
152 38
    private function checkTypesDuplication(array $typeConfigs): void
153
    {
154 38
        $types = array_merge(...array_map('array_keys', $typeConfigs));
155 38
        $duplications = array_keys(array_filter(array_count_values($types), function ($count) {
156 37
            return $count > 1;
157 38
        }));
158 38
        if (!empty($duplications)) {
159
            throw new ForbiddenOverwriteException(sprintf(
160
                'Types (%s) cannot be overwritten. See inheritance doc section for more details.',
161
                implode(', ', array_map('json_encode', $duplications))
162
            ));
163
        }
164 38
    }
165
166 40
    private function mappingConfig(array $config, ContainerBuilder $container): array
167
    {
168
        // use default value if needed
169 40
        $config = array_replace_recursive(self::$defaultDefaultConfig, $config);
170
171 40
        $mappingConfig = $config['definitions']['mappings'];
172 40
        $typesMappings = $mappingConfig['types'];
173
174
        // app only config files (yml or xml or graphql)
175 40
        if ($mappingConfig['auto_discover']['root_dir'] && $container->hasParameter('kernel.root_dir')) {
176 1
            $typesMappings[] = ['dir' => $container->getParameter('kernel.root_dir').'/config/graphql', 'types' => null];
177
        }
178 40
        if ($mappingConfig['auto_discover']['bundles']) {
179 4
            $mappingFromBundles = $this->mappingFromBundles($container);
180 4
            $typesMappings = array_merge($typesMappings, $mappingFromBundles);
181
        }
182 40
        if($mappingConfig['auto_discover']['built_in']) {
183 39
            $typesMappings[] = [
184 39
                'dir' => $this->bundleDir(OverblogGraphQLBundle::class).'/Resources/config/graphql',
185
                'types' => ['yaml'],
186
            ];
187
        }
188
189
        // from config
190 40
        $typesMappings = $this->detectFilesFromTypesMappings($typesMappings, $container);
191
192 40
        return $typesMappings;
193
    }
194
195 40
    private function detectFilesFromTypesMappings(array $typesMappings, ContainerBuilder $container): array
196
    {
197 40
        return array_filter(array_map(
198 40
            function (array $typeMapping) use ($container) {
199 39
                $suffix = $typeMapping['suffix'] ?? '';
200 39
                $types = $typeMapping['types'] ?? null;
201
202 39
                return $this->detectFilesByTypes($container, $typeMapping['dir'], $suffix, $types);
203
            },
204 40
            $typesMappings
205
        ));
206
    }
207
208 4
    private function mappingFromBundles(ContainerBuilder $container): array
209
    {
210 4
        $typesMappings = [];
211 4
        $bundles = $container->getParameter('kernel.bundles');
212
213
        // auto detect from bundle
214 4
        foreach ($bundles as $name => $class) {
215
            // skip this bundle
216 1
            if($class === OverblogGraphQLBundle::class) {
217 1
                continue;
218
            }
219
220 1
            $bundleDir = $this->bundleDir($class);
221
222
            // only config files (yml or xml)
223 1
            $typesMappings[] = ['dir' => $bundleDir.'/Resources/config/graphql', 'types' => null];
224
        }
225
226 4
        return $typesMappings;
227
    }
228
229 39
    private function detectFilesByTypes(ContainerBuilder $container, string $path, string $suffix, array $types = null): array
230
    {
231
        // add the closest existing directory as a resource
232 39
        $resource = $path;
233 39
        while (!is_dir($resource)) {
234 1
            $resource = dirname($resource);
235
        }
236 39
        $container->addResource(new FileResource($resource));
237
238 39
        $stopOnFirstTypeMatching = empty($types);
239
240 39
        $types = $stopOnFirstTypeMatching ? array_keys(self::SUPPORTED_TYPES_EXTENSIONS) : $types;
241 39
        $files = [];
242
243 39
        foreach ($types as $type) {
244 39
            $finder = Finder::create();
245
            try {
246 39
                $finder->files()->in($path)->name(sprintf('*%s.%s', $suffix, self::SUPPORTED_TYPES_EXTENSIONS[$type]));
247 1
            } catch (InvalidArgumentException $e) {
248 1
                continue;
249
            }
250 39
            if ($finder->count() > 0) {
251 39
                $files[] = [
252 39
                    'type' => $type,
253 39
                    'files' => $finder,
254
                ];
255 39
                if ($stopOnFirstTypeMatching) {
256 1
                    break;
257
                }
258
            }
259
        }
260
261 39
        return $files;
262
    }
263
264
    /**
265
     * @throws ReflectionException
266
     */
267 39
    private function bundleDir(string $bundleClass): string
268
    {
269 39
        $bundle = new ReflectionClass($bundleClass); // @phpstan-ignore-line
270
271 39
        return dirname($bundle->getFileName());
272
    }
273
274 40
    private function getAliasPrefix(): string
275
    {
276 40
        return 'overblog_graphql';
277
    }
278
279 40
    private function getAlias(): string
280
    {
281 40
        return $this->getAliasPrefix().'_types';
282
    }
283
}
284