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
Push — 0.14 ( 6c4d30 )
by Jérémiah
97:45 queued 93:57
created

ConfigParserPass::getConfigs()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 4

Importance

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