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 — master (#590)
by Jérémiah
06:50
created

ConfigParserPass::getAlias()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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