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

Passed
Pull Request — 0.14 (#841)
by Jérémiah
02:54
created

ConfigParserPass::mappingConfig()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5.009

Importance

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